面试题系列:Java 夺命连环16问
作者 | 前阿里技术专家 - 艾小仙
1.说说进程和线程的区别?
2.知道 synchronized 原理吗?
-
当多个线程进入同步代码块时,首先进入entryList
-
有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1
-
如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁
-
如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null
3.那锁的优化机制了解吗?
4.那对象头具体都包含哪些内容?
-
对象头
-
实例数据
-
对齐填充
-
对象自身运行时所需的数据,也被称为Mark Word,也就是用于轻量级锁和偏向锁的关键点。具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID、偏向锁时间戳。
-
存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例。
5.对于加锁,那再说下 ReentrantLock 原理?他和 synchronized 有什么区别?
-
等待可中断,当持有锁的线程长时间不释放锁的时候,等待中的线程可以选择放弃等待,转而处理其他的任务。
-
公平锁:synchronized和ReentrantLock默认都是非公平锁,但是ReentrantLock可以通过构造函数传参改变。只不过使用公平锁的话会导致性能急剧下降。
-
绑定多个条件:ReentrantLock可以同时绑定多个Condition条件对象。
6.CAS 的原理呢?
-
变量内存地址,V表示
-
旧的预期值,A表示
-
准备设置的新值,B表示
7.那么 CAS 有什么缺点吗?
8.说说 HashMap 原理吧?
9.那多线程环境怎么使用 Map 呢?ConcurrentHashmap 了解过吗?
-
计算hash,定位到segment,segment如果是空就先初始化
-
使用ReentrantLock加锁,如果获取锁失败则尝试自旋,自旋超过次数就阻塞获取,保证一定获取锁成功
-
遍历HashEntry,就是和HashMap一样,数组中key和hash一样就直接替换,不存在就再插入链表,链表同样
-
首先计算hash,遍历node数组,如果node是空的话,就通过CAS+自旋的方式初始化
-
如果当前数组位置是空则直接通过CAS自旋写入数据
-
如果hash==MOVED,说明需要扩容,执行扩容
-
如果都不满足,就使用synchronized写入数据,写入数据同样判断链表、红黑树,链表写入和HashMap的方式一样,key hash一样就覆盖,反之就尾插法,链表长度超过8就转换成红黑树
10.volatile 原理知道吗?
-
StoreStore屏障,保证上面的普通写不和volatile写发生重排序
-
StoreLoad屏障,保证volatile写与后面可能的volatile读写不发生重排序
-
LoadLoad屏障,禁止volatile读与后面的普通读重排序
-
LoadStore屏障,禁止volatile读和后面的普通写重排序
11.那么说说你对 JMM 内存模型的理解?为什么需要 JMM?
-
单线程每个操作,happen-before于该线程中任意后续操作
-
volatile写happen-before与后续对这个变量的读
-
synchronized解锁happen-before后续对这个锁的加锁
-
final变量的写happen-before于final域对象的读,happen-before后续对final变量的读
-
传递性规则,A先于B,B先于C,那么A一定先于C发生
12.说了半天,到底工作内存和主内存是什么?
13.说说 ThreadLocal 原理?
14.那引用类型有哪些?有什么区别?
-
强引用指的就是代码中普遍存在的赋值方式,比如A a = new A()这种。强引用关联的对象,永远不会被GC回收。
-
软引用可以用SoftReference来描述,指的是那些有用但是不是必须要的对象。系统在发生内存溢出前会对这类引用的对象进行回收。
-
弱引用可以用WeakReference来描述,他的强度比软引用更低一点,弱引用的对象下一次GC的时候一定会被回收,而不管内存是否足够。
-
虚引用也被称作幻影引用,是最弱的引用关系,可以用PhantomReference来描述,他必须和ReferenceQueue一起使用,同样的当发生GC的时候,虚引用也会被回收。可以用虚引用来管理堆外内存。
15.线程池原理知道吗?
-
最大线程数maximumPoolSize
-
核心线程数corePoolSize
-
活跃时间keepAliveTime
-
阻塞队列workQueue
-
拒绝策略RejectedExecutionHandler
-
当我们提交任务,线程池会根据corePoolSize大小创建若干任务数量线程执行任务
-
当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队
-
当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize额外创建的线程等待keepAliveTime之后被自动销毁
-
如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理
16.拒绝策略有哪些?
-
AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
-
CallerRunsPolicy:只用调用者所在的线程来处理任务
-
DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
-
DiscardPolicy:直接丢弃任务,也不抛出异常