HashTable的线程安全
HashTable集合使用synchronized关键字来保证线程安全,但如果线程竞争很激烈,即同时有多个线程访问同一个HashTable对象,HashTablede效率就会变得非常低下。因为HashTable中的同步方法是由synchronized关键字修饰的,作用的是对象,当一个线程访问HashTable的方法时,其他线程访问HashTable的同步方法时,其他线程就会等待,直至当前线程访问结束。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap的线程安全
首先我们了解下ConcurrentHashMap的底层数据结构
由上图我们可以看到,ConcurrentHashMap底层其实是采用数组+数组+链表的形式来存放数据。
将哈希表分成多个segment片段存储在segments数组中,而每个segement对象中存储的是table数组,存储的是HashEntry类型的数据,而这些数据又以链表的形式存储着。
static final class Segment<K,V> extends ReentrantLock implements Serializable
由源码可以看出,ConcurrentHashMap中的Segment这个内部类继承了 ReentrantLock类,说明它具有重入锁的特性。每个segment片段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。这样就大大提高了在多线程访问下的效率问题。
jdk1.8下的ConcurrentHashMap
jdk1.8中ConcurrentHashMap参考了jdk1.8 HashMap的实现,采用了数组+链表+红黑树的实现方式来设计,jdk1.8中彻底放弃了Segment转而采用的是Node,其设计思想也不再是JDK1.7中的分段锁思想。
Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。
相对于jdk1.7做出的改进:
- 数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
- 保证线程安全机制:采用CAS+Synchronized保证线程安全。
- 加锁方式:对每个数组元素加锁(Node)