目录
ConcurrentHashMap的锁分段技术可有效提升并发访问率
为什么1.8用synchronized而不是ReentranLock
CopyOnWriteArrayList 读取和写入源码简单分析
ConcurrentHashMap的实现原理与使用
为什么要使用ConcurrentHashMap
在并发编程中使用HashMap可能导致程序死循环。而使用线程安全的Hash Table效率又非 常低下,基于以上两个原因,便有了ConcurrentHashMap的登场机会。
线程不安全的HashMap
在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%, 所 以在并发情况下不能使用HashMap。例如,执行以下代码会引起死循环。
HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry表 形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获 取Entry。
效率低下的Hash Table
Hash Table容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下Hash Table 的效率非常低下。因为当一个线程访问Hash Table的同步方法,其他线程也访问Hash Table的同 步方法时,会进入阻塞或轮询状态。如线程l使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap的锁分段技术可有效提升并发访问率
Hash Table容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问Hash Table的 线程都必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么 当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并 发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。
首先将数据分成一段一段地存 储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数 据也能被其他线程访问。
ConcurrentHashMap的结构
注意:本文描述的conurrenthashmap是jdk1.7版本的
通过ConcurrentHashMap的类图来分析ConcurrentHashMap的结构,如图所示。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。
Segment是一种可重 入锁(ReentrantLock), 在ConcurrentHashMap里扮演锁的角色;
ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
HashEntry则用于存储键值对数 据。
一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种 数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元 素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时, 必须首先获得与它对应的Segment锁。
ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。如下图是ConcurrentHashMap的内部结构图:
从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。
第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。
ConcurrentHashMap的初始化
会初始化一个Segment数组,容量为16,而每个Segment呢,都继承了ReentrantLock类,也就是说每个Segment类本身就是一个锁,之后Segment内部又有一个table数组,而每个table数组里的索引数据呢,又对应着一个Node链表.
ConcurrentHashMap初始化方法是通过initialCapacity、loadFactor和concurrencyLevel等几个 参数来初始化segment数组、段偏移量segmentShift, 段掩码segmentMask和每个segment里的 HashEntry数组来实现的。
ConcurrentHashMap的构造函数有3个参数,initialCapacity是整个map的容量,loadFactor是负载,concurrencyLevel是预估的并发线程数,决定了segement数组的长度(大于等于它的2的n次方=ssize)。
每个segement里的HashEntry数组长度可以理解为initialCapacity除以ssize 的倍数C, 如果c大于1, 就会取大于等于c的2的N次方值
初始化segments数组
让我们来看一下初始化segments数组的源代码。