1. ConcurrentHashMap与Hashtable的区别
首先,它们都是线程安全的map容器,是一种将键通过哈希函数计算再映射到某一位置后存储值的数据结构。从底层实现的数据结构上来看,Hashtable是通过数组和链表实现的,而ConcurrentHashMap,在JDK1.7及以前,它是通过Segment分段数组、HashEntry数组和链表实现的,在JDK1.8之后,由数组、链表和红黑树实现的。数组是主体,而链表和红黑树是为了解决哈希冲突而存在的。
从实现线程安全的方式来看,Hashtable通过Synchronized关键字来保证线程安全,效率非常低。当一个线程访问同步方法时,其他线程如果尝试访问同步方法,就会进入阻塞或轮训状态。ConcurrentHashMap在JDK1.7及以前,采用了分段锁的思想来保证线程安全。当线程访问处于某个分段的数据时,线程会获取该分段的锁,其他线程无法再获取,但不影响访问其他分段的数据,提高了并发访问效率。而在JDK1.8之后,采用CAS结合Synchronized的方式保证线程安全。而JDK1.6后Synchronized锁做了很多优化,并且实现过程中锁的粒度更小,并发访问效率更高,这样整体看起来就是优化后且线程安全的HashMap。
2. ConcurrentHashMap在JDK1.7的实现原理是什么
首先,底层数据结构是由Segment数组和HashEntry数组构成。Segment继承了ReentrantLock,所以Segment是一种可重入锁,扮演锁的角色。一个ConcurrentHashMap包含了一个Segment数组,Segment个数一旦初始化就不能改变。Segment数组默认大小是16,意味着最多支持16个线程并发写入。每个Segment都会有一个HashEntry数组,而HashEntry则类似于HashMap中的数据结构,就是一个存储键值对的链表结点。当访问某个结点的数据时,线程必须先获取该结点的Segment锁。也就是说对同一Segment的并发写入会被阻塞,不同Segment的写入是可以并发执行的。此外,数据的读取是不需要获取锁的,这是通过volatile关键字实现的(可见性)。
3. ConcurrentHashMap在JDK1.8的实现原理是什么
首先,底层数据结构和HashMap一样,通过数据、链表和红黑树实现的。而线程安全是通过CAS和Sychronized保证的。当线程插入某个数据时,先通过哈希函数确定其在数组的索引位置,如果该位置为空,就会直接尝试CAS操作来插入结点,无需获取锁,大大提高了并发效率。如果存在头结点,则需要获取首结点的锁(Synchronized),确保其他线程不能同时访问。这样只要不hash冲突,就不会产生并发,也不会影响其他node的读写,效率大幅提升。
4. ConcurrentHashMap在JDK1.7的实现和JDK1.8的实现有什么区别?
- 线程安全实现方式:JDK1.7采用Segment分段锁来保证安全,Segment是继承自ReentrantLock。JDK1.8放弃了Segment分段锁的设计,采用Node+CAS+Synchronized来保证线程安全,锁力度更细,Synchronzied只锁定当前链表和红黑二叉树的首结点。
- Hash冲突解决方法:JDK1.7采用拉链法,JDK1.8则采用拉链法结合红黑树(当链表长度超过一定阈值时,链表会转化为红黑树)
- 并发度:JDK1.7最大并发度是Segment的个数,默认是16。JDK1.8最大并发度是Node数组的大小,并发度更大
5. JDK1.8中什么情况下链表才会转成红黑树进行存储?
当链表长度超过8,并且数组长度超过64时,才会将链表转成红黑树进行存储。如果当前Node数组长度小于阈值MIN_TREEIFY_CAPACITY, 默认为64,则先通过扩大数组容量为原来的两倍以缓解单个链表元素过多的性能问题。
6. JDK1.8中,ConcurrentHashMap的put过程是怎样的?
1️⃣ 检查key和null是否为空,为空抛出异常 2️⃣ 检查数组是否空或长度是否为0,初始化数组 3️⃣ 通过哈希函数计算待插入元素的数组索引,若该位置为空,则采用CAS直接插入结点 4️⃣ 如果正在扩容,则当前线程一起加入到扩容的过程中 5️⃣ 如果插入元素的位置不为空且没在迁移元素,则锁住首结点 6️⃣ 根据该位置的元素是以链表还是红黑树的存储形式,来查找元素 7️⃣ 如果该元素存在,则覆盖旧值 8️⃣ 如果该元素不存在,插入元素,整个Map的元素个数加1,并检查是否需要扩容。
7. ConcurrentHashMap的get方法是否加锁,为什么?
不需要,get方法不涉及对变量的修改,所以会导致并发下可能出问题的原因是共享变量的可见性。但ConcurrentHashMap中,对get方法中用到的共享变量都使用了volatile关键字修饰,保证了不同线程间的内存可见性。所以整个get方法不加锁也不会有任何问题。
8. ConcurrentHashMap默认初始容量是多少?
16
9. ConcurrentHashMap的key,value是否可以为null?
不行。如果key或value为null,会抛出空指针异常。(原因是get返回值为null时的二义性问题, 即没办法确定是存储的值本身为null还是说值不存在) 注意:HashMap允许用null作为key和value,因为HashMap只能单线程下使用,而且还可以用containsKey做二次判断
10. 存储在ConcurrentHashMap中的每个节点是什么样的,有哪些变量?
Node实现了Map.Entry<K,V>接口。里面存放了key,value,hash以及next节点。其中value和next节点使用volatile修饰的,可以保证多线程之间的可见性。