1. 关于java中常用的容器,集合,比如HashMap, ConcurrentHashMap。容器的初始容量,扩容机制,线程安全,JDK8做了哪些优化以及为什么要做这个优化。
HashMap主要涉及的问题:
- 底层数据结构
- 初始化核心参数
- hash算法
- 寻址算法
- hash冲突,解决hash冲突的方法是什么,这里可以说一下ThreadLocal中解决hash冲突的方法,以及为什么这里用链地址法,ThreadLocal中用线性探测法。
- 扩容机制
- put方法,get方法和remove方法执行流程
- hashMap为什么线程不安全
ConcurrentHashMap主要涉及的问题:
- 底层数据结构
- hash算法
- put方法流程,get方法,remove方法,size方法
- jdk1.7和1.8底层实现的区别,包括1.7如何去锁segment
参考文章:
HashMap是头插法还是尾插法 这篇文章讲了为什么要把JDK7的头插法改成尾插法
从存储结构、常用方法分析、扩容以及安全性等方面深入讲解HashMap的工作原理
2. 线程池相关的问题, 最好把线程池的源码看一遍。
线程池中的worker继承AQS,实现了不可重入锁,主要是为了通过有锁,无锁标识worker处于 忙碌状态还是空闲状态,从而可以判断是否对woker进行销毁。参考文章: ThreadPoolExecutor关闭线程池原理
3. AQS的源码过一遍,里面有很多知识点可以总结出来:
参考我的博客: AQS源码总结。
说一下平时常用的,什么地方用到了AQS,比如ConcurrentHashMap中用到了分段锁,对segment进行加锁,Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。
/**
* Segments are specialized versions of hash tables. This
* subclasses from ReentrantLock opportunistically, just to
* simplify some locking and avoid separate construction.
*/
static final class Segment<K,V> extends ReentrantLock implements Serializable {
/**
* The number of elements in this segment's region.
*/
transient volatile int count; // Segment中元素的数量,可见的
/**
* Number of updates that alter the size of the table. This is
* used during bulk-read methods to make sure they see a
* consistent snapshot: If modCounts change during a traversal
* of segments computing size or checking containsValue, then
* we might have an inconsistent view of state so (usually)
* must retry.
*/
transient int modCount; //对count的大小造成影响的操作的次数(比如put或者remove操作)
/**
* The table is rehashed when its size exceeds this threshold.
* (The value of this field is always <tt>(int)(capacity *
* loadFactor)</tt>.)
*/
transient int threshold; // 阈值,段中元素的数量超过这个值就会对Segment进行扩容
/**
* The per-segment table.
*/
transient volatile HashEntry<K,V>[] table; // 链表数组
/**
* The load factor for the hash table. Even though this value
* is same for all segments, it is replicated to avoid needing
* links to outer object.
* @serial
*/
final float loadFactor; // 段的负载因子,其值等同于ConcurrentHashMap的负载因子
...
}
线程池中的内部类Worker,继承了AQS,实现了不可重入锁。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
4. 并发安全集合和线程池知识点, 并发安全集合要了解常用的几种,以及知道在什么场景用。CopyOnWriteArrayList用的是写时复制的思想,多用于读多写少的场景,在Redis的持久化机制中,Fork出的子进程跟父进程是共享代码空间,数据空间不共享。用的也是写时复制的思想。
参考博客: 并发安全集合和线程池
5. volatile, cas和synchronized原理
重点说一下思路:
- 为了解决处理器和主存之间的速度鸿沟,引入高速缓存,却又导致了缓存一致性问题
- 为了解决缓存一致性问题,引入了如MESI等技术,又导致了处理器等待问题
- 为了解决处理器等待问题,引入了写缓冲区和无效队列,又导致了重排序和可见性问题
- 为了解决重排序和可见性问题,引入了内存屏障
- 每个技术点都可以举例工作中的场景或者源码中的场景,知道它的原理和用法。比如volatile(AQS中state concurrentHashMap中的Node节点),线程池的ctl(atomic 高三位:线程池状态 低位:线程池worker线程数量), concurrentHashMap(cas+synchronized),工作中优化停机,心跳时间等。
技巧: 参考文章,jstack分析线程等待、死锁问题关于synchronized原理,可以结合jstack线程追踪命令讲解下。我们在用jstack进行线程追踪的时候会看到线程的状态信息,比如:
NEW,未启动的。不会出现在Dump中。
RUNNABLE,在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
BLOCKED,受阻塞并等待监视器锁。被某个锁(synchronizers)給block住了。
WATING,无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
TIMED_WATING,有时限的等待另一个线程的特定操作。和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。
TERMINATED,已退出的。
还可以看到线程状态产生的原因,这块就可以结合上面那篇文章讲一下synchronized的原理
runnable:状态一般为RUNNABLE。
in Object.wait():等待区等待,状态为WAITING或TIMED_WAITING。
waiting for monitor entry:进入区等待,状态为BLOCKED。
waiting on condition:等待区等待、被park。
sleeping:休眠的线程,调用了Thread.sleep()。
6. ThreadLocal相关的知识点
说ThreadLocal的时候,可以讲一下里面的几个关键点:
1.包括弱引用,空间整理,解决哈希冲突的方法等;
2. 说一下ThreadLocal的缺陷,没法解决父子线程间值传递;
3. 说一下InheritableThreadLocal,讲一下InheritableThreadLocal怎么解决这个问题的,以及它又会存在如果线程复用的话,无法传递值的问题;
4. 为了解决这个问题,阿里开源了TransmittableThreadLocal,用于解决“在使用线程池等会缓存线程的组件情况下传递ThreadLocal”问题的InheritableThreadLocal扩展。如果要用Ttl在线程池与主线程间传递,需要配合TtlRunnable和TtlCallable使用。
5. 最后,看一下这篇文章,ThreadLocal空间整理,可以知道threadlocal虽然解决了线程之间的变量隔离,但是存在一些缺陷,为了解决这个问题,FastThreadLocal用空间换时间,可以对ThreadLocal的操作时间进行优化。参考文章:Netty源码分析——FastThreadLocal框架设计
6. 看下这篇文章,专属成自己的语言,可以说成是你自己项目中遇到并且解决的。细数ThreadLocal三大坑,内存泄漏仅是小儿科
7. JVM相关知识点——内存区域
参考文章: 聊聊JVM的垃圾收集
主要介绍分为那几块模型以及1.7和1.8方法区的位置,从放堆中移到了本地内存,这块区域不受JVM控制。
8. JVM相关知识点——如何判断对象是否可以被回收
参考文章: 聊聊JVM的垃圾收集
介绍一下两种方法,引用计数法和可达性分析法。说一下这两种方法的区别。
关于可达性分析法,需要从GC Root往下搜索,需要知道哪些对象可以作为GC Root。
9. JVM相关知识点——常见的垃圾收集算法
参考文章:聊聊JVM的垃圾收集