前言
众所周知HashMap是非线程安全的,而ConcurrentHashMap是线程安全的,它是目前使用得最多的线程安全map,那么它是如何保证线程安全呢。让我们来一起看一下。
一、HashMap的线程安全问题
在单线程时,HashMap无疑是使用得最多的,哈希算法时间复杂度相对较低,使用高效。但是在多线程的情况下HashMap是非线程安全的。因为在多线程的情况下HashMap的resize()方法容易容易产生环形链表,造成死循环。所以在多线程的情况下,我们需要使用线程安全的map。
二、线程安全的map
1.线程安全的Hashtable
Hashtable是我们最先接触到的线程安全的map,但是在多线程中,因为其效率过低,我们不建议使用。
那么它效率低的原因是为什么呢?
因为Hashtable中对map的操作都使用synchronized上锁了,那么我们每次只能有一个线程对map进行操作
我们看一下Hashtable源码(jdk1.8):
public synchronized int size() {
return count;
}
public synchronized boolean isEmpty() {
return count == 0;
}
public synchronized V put(K key, V value) {
//省略
}
public synchronized V remove(Object key) {
//省略
}
//省略
大多数操作的方法都使用了synchronized,而synchronized修饰方法,在一个线程使用该方法时就是锁住了对象,其他线程该对象其他的方法的调用将会被禁止。那么在高并发场景下,多个线程等待一个线程运行完毕是十分影响效率的。所以Hashtable使用较少。
2. ConcurrentHashMap
ConcurrentHashMap也是一种线程安全的map,相对于Hashtable的涉及线程安全的方法都使用synchronized上锁。ConcurrentHashMap的锁就上得更有艺术一些。为了解决一个线程访问方法的低效率问题,ConcurrentHashMap使用了分段锁(jdk1.5)和 synchronized+CAS(jdk1.8)。
分段锁
在jdk1.5之后,1.8之前,ConcurrentHashMap使用的是分段锁的方法,简而言之就是ConcurrentHashMap中启用了segment数组,并将map的元素通过再散列均匀的分布到segment中,一个segment就是一个锁。segment相当于一个小的hashmap,里面是数组+链表。通过把大的Hashmap分组,每个组使用一个锁,来缓解多线程访问被禁止的问题。那么不同的线程对不同的segment的操作是不会被禁止的。
锁 + CAS
在jdk1.8之后 , ConcurrentHashMap的实现方法又从分段锁改为CAS+锁。从设置多个ReentranLock锁对象变为在直接锁链表的第一个元素,如果链表为空,就无需锁,直接用CAS插入。
代码如下:
public V put(K key, V value) {
return putVal