什么是锁分段技术?
ConcurrentHashMap使用的是锁分段技术,锁分段技术就是说容器里有多把锁,每一把锁用于锁容器中的一部分数据,当有多个线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率。然后在ConcurrentHashMap中,它首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment继承了ReentrantLock,是一种可重入锁(可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁)。Segment在ConcurrentHashMap里扮演锁的角色,HashEntry则用于键值对的存储。一个ConcurrentHashMap里包含一个Segment数组,segment是一种数组和链表的数据结构, 一个Segment里包含一个HashEntry数组,HashEntry也是一个数组和链表结构的元素。每个Segment守护着一个HashEntry数组里面的元素,当对HashEntry数组里面的数据进行修改时,首先必需获得它对应的Segment锁。
put方法需要对共享变量进行写入操作,为了线程安全,在操作共享变量时必须得加锁。Put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
是否需要扩容:
在插入元素前会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阀值,则数组进行扩容。值得一提的是,ConcurrentHashMap的扩容判断比HashMap更恰当,因为HashMap是在插入元素后判断数组是否已经到达容量的,如果到达了就进行扩容,但是很有可能扩容之后没有新元素插入,这时HashMap就进行了一次无效的扩容。
如何扩容:
扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行重hash后插入到新的数组里。为了高效,ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment里的HashEntry数组进行扩容。