大数据面试题题目2020年底总结Java(二)

大数据面试题题目2020年底总结Java(二)

1. 多线程

1.1volatile

  1. 内存模型可见性

用volatile修饰的变量,就会具有可见性。
volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。
但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性

  1. volatile不保证原子性
  2. 如何解决
  3. 指令重排

指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理
内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)

volatile 是因为其本身包含“禁止指令重排序”的语义
synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作
编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整(CPU乱序执行)

把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。大数据面试题题目2020年底总结Java(二)
对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。
如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步

1.2 CAS

  1. 是什么

Compare and Swap,即比较再交换
jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁

  1. 底层原理

CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。
当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。

当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升

1)首先从地址 V 读取值 A;2)根据 A 计算目标值 B;3)通过 CAS 以原子的方式将地址 V 中的值从 A 修改为 B。

  1. 缺点

循环时间长开销很大。
只能保证一个变量的原子操作。
ABA问题。

  1. ABA问题

在第1步中读取的值是A,并且在第3步修改成功了,我们就能说它的值在第1步和第3步之间没有被其他线程改变过了吗?
如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题
Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

1.3 线程池

  1. 是什么

线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务

  1. 常见类型

ThreadPoolExecutor:
newSingleThreadExecutor --创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool --创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool --创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
newCachedThreadPool --创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  1. 7大参数
  2. 底层原理
  3. 四种拒绝策略理论

1.4 线程切换代价

程序计数器(记录要执行的下一条指令),
一组寄存器(保存当前线程的工作变量),
堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。

寄存器 是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置

2. Java

2.1 引用

  1. 强引用

强引用是使用最普遍的引用。如果一个对象具有强引用,那么垃圾回收器绝不会回收它

  1. 软引用

软引用是除了强引用外,最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用
一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。
当堆使用率临近阈值时,才会去回收软引用的对象。因此,软引用可以用于实现对内存敏感的高速缓存。
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;
当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据

  1. 弱引用

弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收
在java中,可以用java.lang.ref.WeakReference实例来保存对一个Java对象的弱引用

  1. 虚引用

虚引用是所有类型中最弱的一个。
一个持有虚引用的对象和没有引用几乎是一样的,随时可能被垃圾回收器回收,当试图通过虚引用的get()方法取得强引用时,总是会失败。
并且虚引用必须和引用队列一起使用,它的作用在于检测对象是否已经从内存中删除,跟踪垃圾回收过程

  1. 引用队列

将这个虚引用加入引用队列。
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动

2.2 泛型

集成
接口和接口实现类

2.3 stream流

  1. Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
  2. 这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作
  3. Stream不保存数据,故每个Stream流只能使用一次

在Stream流上的操作,可以分成两种:Intermediate(中间操作)和Terminal(终止操作)

从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。unordered()方法可以解除有序流的顺序限制,更好地发挥并行处理的性能优势,例如distinct将保存任意一个唯一元素而不是第一个,limit将保留任意n个元素而不是前n个

中间操作
filter(Predicate)
将结果为false的元素过滤掉
map(fun)
转换元素的值,可以用方法引元或者lambda表达式
flatMap(fun)
若元素是流,将流摊平为正常元素,再进行元素转换
limit(n)
保留前n个元素
skip(n)
跳过前n个元素
distinct()
剔除重复元素
sorted()
将Comparable元素的流排序
sorted(Comparator)
将流元素按Comparator排序
peek(fun)
流不变,但会把每个元素传入fun执行,可以用作调试

终结操作
max(Comparator)
min(Comparator)
count()
findFirst()
返回第一个元素
findAny()
返回任意元素
anyMatch(Predicate)
任意元素匹配时返回true
allMatch(Predicate)
所有元素匹配时返回true
noneMatch(Predicate)
没有元素匹配时返回true
reduce(fun)
从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数
reduce(a, fun)
a为幺元值,作为累积器的起点
reduce(a, fun1, fun2)
与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并

搜集操作
iterator()
forEach(fun)
forEachOrdered(fun)
可以应用在并行流上以保持元素顺序
toArray()
toArray(T[] :: new)
返回正确的元素类型
collect(Collector)
collect(fun1, fun2, fun3)
fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来

2.4 IO流

  1. 字符流
  2. 缓冲流
  3. 转换流
  4. 序列化流
  5. 打印流
    大数据面试题题目2020年底总结Java(二)
    大数据面试题题目2020年底总结Java(二)
    大数据面试题题目2020年底总结Java(二)
    大数据面试题题目2020年底总结Java(二)

2.5 socket

2.5.1 网络四层协议

大数据面试题题目2020年底总结Java(二)

  • 应用层
  • 传输层
  • 网络层
  • 链路层

2.5.2 UDP和TCP协议

  1. UDP

UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。
在OSI模型中,在第四层——传输层,处于IP协议的上一层。
UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。

  1. 面向无连接
  2. 有单播,多播,广播的功能
  3. UDP是面向报文的
  4. 不可靠性
  5. 头部开销小,传输数据报文时是很高效的。
  1. TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议
  1. TCP连接过程
    大数据面试题题目2020年底总结Java(二)

第一次握手
客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。
第二次握手
服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。
第三次握手
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。

为什么 TCP 建立连接需要三次握手,而不是两次?
这是因为这是为了防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误

大数据面试题题目2020年底总结Java(二)
TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。
第一次握手
若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。
第二次握手
B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。
第三次握手
B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。
第四次握手
A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。

2.5.3 socket

  1. 端口和主机名
  2. 客户端和服务端
    大数据面试题题目2020年底总结Java(二)

2.6 NIO

  1. Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式

Java NIO: Channels and Buffers(通道和缓冲区)
标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

Java NIO: Non-blocking IO(非阻塞IO)
Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

Java NIO: Selectors(选择器)
Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

2.7 反射

  1. classForName

Class.forName(String className)使用装载当前类的类装载器来装载制定的类,因为class.forName(String name)方法内部调用了Class.forName(className,true,this.getClass().getClassLoader())方法

  1. classLoader

classLoader.loadClass(StringclassName,boolean resolve);需要手动制定装载器的实例。
ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中

  1. 双亲委派

要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,
请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码
大数据面试题题目2020年底总结Java(二)
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

  1. 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次
  2. 其次是考虑到安全因素,java核心api中定义类型不会被随意替换
  3. 在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常

2.8 注解

2.9动态代理

3. 设计模式

  1. 单例
  2. 工厂
  3. builder
  4. 代理
  5. 中介者

4. 集合

  1. hashmap
  1. 底层数组+链表实现,可以存储null键和null值,线程不安全
  2. 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
  3. 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
    插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
  4. 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
  5. 计算index方法:index = hash & (tab.length – 1)
  1. 哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。
  2. 加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。
  3. 空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。
  1. hashtable
  1. 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
  2. 初始size为11,扩容:newsize = olesize*2+1
  3. 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

容量(capacity):hash表中桶的数量
初始化容量(initial capacity):创建hash表时桶的数量,HashMap允许在构造器中指定初始化容量
尺寸(size):当前hash表中记录的数量
负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的散列表,依此类推。轻负载的散列表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢)
负载极限,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing

  1. cocurrenthashmap

底层采用分段的数组+链表实现,线程安全
通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)

Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

Hashtable中,无论是key还是value都不能为null。
hashmap的key和value可是是null

锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。
ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的

  1. arraylist和linkedlist
  • array

Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。
Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据, (因为删除数据以后, 需要把后面所有的数据前移)
缺点: 数组初始化必须指定初始化的长度, 否则报错

  • arraylist

可以看作是能够自动增长容量的数组
ArrayList底层的实现是Array, 数组扩容实现

新增数据空间判断
新增数据的时候需要判断当前是否有空闲空间存储
扩容需要申请新的连续空间
把老的数组复制过去
新加的内容
回收老的数组空间

当前数组是由默认构造方法生成的空数组并且第一次添加数据。此时minCapacity等于默认的容量(10)那么根据下面逻辑可以看到最后数组的容量会从0扩容成10。而后的数组扩容才是按照当前容量的1.5倍进行扩容;

当前数组是由自定义初始容量构造方法创建并且指定初始容量为0。此时minCapacity等于1那么根据下面逻辑可以看到最后数组的容量会从0变成1。这边可以看到一个严重的问题,一旦我们执行了初始容量为0,那么根据下面的算法前四次扩容每次都 +1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容。

当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError(内存溢出)否则的话根据与MAX_ARRAY_SIZE的比较情况确定是返回Integer最大值还是MAX_ARRAY_SIZE。这边也可以看到ArrayList允许的最大容量就是Integer的最大值(-2的31次方~2的31次方减1)

  • linkedlist

是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些对比都是指数据量很大或者操作很频繁

  1. treeset和treemap

5. 锁

  1. 公平和非公平锁
  2. 独占锁和共享锁

独占锁很明显就是持锁的线程只能有一个,共享锁则可以有多个
共享锁是为了提高程序的效率,
举个例子数据的操作有读写之分,对于写的操作加锁,保证数据正确性,而对于读的操作如果不加锁,在写读操作同时进行时,读的数据有可能不是最新数据,如果对读操作加独占锁,面对读多写少的程序肯定效率很低,所有就出现了共享锁,对于读的的操作就使用共享的概念,但是对于写的操作则是互斥的,保证了读写的数据操作都一致,在java中上述的锁叫读写锁

在java中读写锁(ReadWritelock)的机制是基于AQS的一种实现,保证读读共享,读写互斥,写写互斥,如果要说机制的话,还要从AQS说起,这是java实现的一种锁机制,
互斥锁,读者写锁,条件产量,信号量,栅栏的都是它的衍生物,主要工作基于CHL队列,voliate关键字修饰的状态符stat,线程去修改状态符成功了就是获取成功,失败了就进队列等待,等待唤醒,
AQS中还有很重要的一个概念是自旋,在等待唤醒的时候,很多时候会使用自旋(while(!cas()))的方式,不停的尝试获取锁,直到被其他线程获取成功

共享与独占的区别就在于,CHL队列中的节点的模式是EXCLUSIVE还是SHARED,
当一个线程成功修改了stat状态,表示获取了锁,如果线程所在的节点为SHARED,将开始一个读锁传递的过程,从头结点,向队列后续节点传递唤醒,直到队列结束或者遇到了EXCLUSIVE的节点,等待所有**的读操作完成,然后进入到独享模式(这部分尽力了,大家还是看源码)
公平与非公平的区别就在于线程第一次获取锁时,也就是执行修改stat操作时,是进队列还是直接修改状态,这是基本的工作机制

  1. 乐观锁和悲观锁

悲观锁,主要是表锁,行锁还有间隙锁,叶锁,读锁,因为这些锁在被触发的时候势必引起线程阻塞,所以叫悲观
乐观锁其实在mysql本身中不存在的,但是mysql提供了种mvcc的机制,支持乐观锁机制,

在innodb引擎下存在,mvcc是为了满足事务的隔离,通过版本号的方式,避免同一数据不同事务间的竞争,所说的乐观锁只在事务级别为读未提交读提交,才会生效
多版本并发控制,保证数据操作在多线程过程中,保证事务隔离的机制,可以降低锁竞争的压力,保证比较高并发量,这个过程。
在每开启一个事务时,会生成一个事务的版本号,被操作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号,更新到此数据行(永久)中,这样保证了每个事务操作的数据,都是相互不影响的,也不存在锁的问题

  1. 可重入锁和递归锁
  2. 自旋锁
  3. 读写锁
  4. 信号量
  5. countdown latch
  6. cyclic barrier
  7. 阻塞队列
    大数据面试题题目2020年底总结Java(二)
  8. synchronized和lock

在java中,synchronized关键字,是语言自带的,也叫内置锁,synchronized关键字,我们都知道被synchronized修饰的方法或者代码块,在同一时间内,只允许一个线程执行,是明显的独享锁,synchronized的实现机制?可以参考AQS的实现方式,只是AQS使用显示的用lock.lock()调用,而sync作为关键字修饰,你可以认为在synchronized修饰的地方,自动添加了lock方法,结束的地方进行了unlock释放锁的方法,只是被隐藏了,我们看不到。

它本身实现有两部分:monitor对象,线程,工作机制还是线程抢占对象使用权,对象都有自己的对象头,存储了对象的很多信息,其中有一个是标识被哪个线程持有,对比AQS,线程从修改stat,变为修改monitor的对象头,线程的等待区域动 AQS中的队列,变为monitor对象中的某个区域

  1. callable接口,thread,runnable
  2. 死锁定位和解决
  3. 事务

事务常说一系列操作作为一个整体要么都成功要么都失败,主要特性acid,
事务的的实现主要依赖两个log redo-log,undo-log,
每次事务都会记录数据修改前的数据undo-log,修改后的数据放入redo-log,
提出成功则使用redo-log 更新到磁盘,失败则使用undo-log将数据恢复到事务之前的数据

6. GC

  1. jvm结构
  2. jvm内存区分算法
  3. jvm回收算法
  4. jvm回收器
  5. gc roots
  6. jvm

VM用来区别线程栈和堆的内存方式,每个线程在运行的时候,所操作的数据存储空间有两个,
一个是主内存 一个是工作内存,
主内存其实就是jvm中堆,
工作内存就是线程的栈,
每次的数据操作,都是从主内存中把数据读到工作内存中,然后在工作内存中进行各种处理,如果进行了修改,会把数据回写到主内存,然后其他线程又进行同样的操作,就这样数据在工作内存和主内存,进进出出,不亦乐乎,
在多次的情况下,就是因为进进出出的顺序乱了,不是按照线程预期的访问顺序,就出现了数据不一致的问题,导致了多线程的不安全性;整个操作过程还牵涉到CPU,高速缓存等概念

7. 数据结构与算法

  1. 红黑树
    大数据面试题题目2020年底总结Java(二)

红黑树也是二叉查找树,我们知道,二叉查找树这一数据结构并不难,而红黑树之所以难是难在它是自平衡的二叉查找树
大数据面试题题目2020年底总结Java(二)大数据面试题题目2020年底总结Java(二)

  1. 链表

链表是否有环–快慢指针法大数据面试题题目2020年底总结Java(二)

  1. 数组
  2. 哈希表
  3. B+树
  4. B树
  5. B-树