1717
1818- [ 概述] ( #概述 )
1919- [ ConcurrentHashMap] ( #concurrenthashmap )
20+ - [ 要点] ( #要点 )
21+ - [ 源码] ( #源码 )
22+ - [ 示例] ( #示例 )
2023- [ CopyOnWriteArrayList] ( #copyonwritearraylist )
24+ - [ 要点] ( #要点-1 )
25+ - [ 源码] ( #源码-1 )
26+ - [ 示例] ( #示例-1 )
2127- [ 资料] ( #资料 )
2228
2329<!-- /TOC -->
@@ -26,31 +32,339 @@ tags:
2632
2733JDK 的 ` java.util.concurrent ` 包(即 juc)中提供了几个非常有用的并发容器。
2834
29- | 类名 | 类型 | 功能 |
30- | --------------------- | ----- | --------------------------------------------------------------------------------------------------- |
31- | CopyOnWriteArrayList | List | 线程安全的 ArrayList |
32- | CopyOnWriteArraySet | Set | 线程安全的 Set,它内部包含了一个 CopyOnWriteArrayList,因此本质上是由 CopyOnWriteArrayList 实现的。 |
33- | ConcurrentSkipListSet | Set | 相当于线程安全的 TreeSet。它是有序的 Set。它由 ConcurrentSkipListMap 实现。 |
34- | ConcurrentHashMap | Map | 线程安全的 HashMap。采用分段锁实现高效并发。 |
35- | ConcurrentSkipListMap | Map | 线程安全的有序 Map。使用跳表实现高效并发。 |
36- | ConcurrentLinkedQueue | Queue | 线程安全的无界队列。底层采用单链表。支持 FIFO。 |
37- | ConcurrentLinkedDeque | Queue | 线程安全的无界双端队列。底层采用双向链表。支持 FIFO 和 FILO。 |
38- | ArrayBlockingQueue | Queue | 数组实现的阻塞队列。 |
39- | LinkedBlockingQueue | Queue | 链表实现的阻塞队列。 |
40- | LinkedBlockingDeque | Queue | 双向链表实现的双端阻塞队列。 |
35+ * CopyOnWriteArrayList - 线程安全的 ArrayList
36+ * CopyOnWriteArraySet - 线程安全的 Set,它内部包含了一个 CopyOnWriteArrayList,因此本质上是由 CopyOnWriteArrayList 实现的。
37+ * ConcurrentSkipListSet - 相当于线程安全的 TreeSet。它是有序的 Set。它由 ConcurrentSkipListMap 实现。
38+ * ConcurrentHashMap - 线程安全的 HashMap。采用分段锁实现高效并发。
39+ * ConcurrentSkipListMap - 线程安全的有序 Map。使用跳表实现高效并发。
40+ * ConcurrentLinkedQueue - 线程安全的无界队列。底层采用单链表。支持 FIFO。
41+ * ConcurrentLinkedDeque - 线程安全的无界双端队列。底层采用双向链表。支持 FIFO 和 FILO。
42+ * ArrayBlockingQueue - 数组实现的阻塞队列。
43+ * LinkedBlockingQueue - 链表实现的阻塞队列。
44+ * LinkedBlockingDeque - 双向链表实现的双端阻塞队列。
4145
4246## ConcurrentHashMap
4347
48+ ### 要点
49+
4450* 作用:ConcurrentHashMap 是线程安全的 HashMap。
45- * 原理:ConcurrentHashMap 采用了分段锁机制实现高效的并发访问。
51+ * 原理:JDK6 与 JDK7 中,ConcurrentHashMap 采用了分段锁机制。JDK8 中,摒弃了锁分段机制,改为利用 CAS 算法。
52+
53+ ### 源码
54+
55+ #### JDK7
56+
57+ ConcurrentHashMap 类在 jdk1.7 中的设计,其基本结构如图所示:
58+
59+ <p align =" center " >
60+ <img src =" https://raw.githubusercontent.com/dunwu/javase-notes/master/images/concurrent/ConcurrentHashMap-jdk7.png " >
61+ </p >
62+
63+ 每一个 segment 都是一个 HashEntry<K,V>[ ] table, table 中的每一个元素本质上都是一个 HashEntry 的单向队列。比如 table[ 3] 为首节点,table[ 3] ->next 为节点 1,之后为节点 2,依次类推。
64+
65+ ``` java
66+ public class ConcurrentHashMap <K, V> extends AbstractMap<K , V >
67+ implements ConcurrentMap<K , V > , Serializable {
68+
69+ // 将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对
70+ // 不属于同一个片段的节点可以并发操作,大大提高了性能
71+ final Segment<K ,V > [] segments;
72+
73+ // 本质上Segment类就是一个小的hashmap,里面table数组存储了各个节点的数据,继承了ReentrantLock, 可以作为互拆锁使用
74+ static final class Segment <K,V> extends ReentrantLock implements Serializable {
75+ transient volatile HashEntry<K ,V > [] table;
76+ transient int count;
77+ }
78+
79+ // 基本节点,存储Key, Value值
80+ static final class HashEntry <K,V> {
81+ final int hash;
82+ final K key;
83+ volatile V value;
84+ volatile HashEntry<K ,V > next;
85+ }
86+ }
87+ ```
88+
89+ #### JDK8
90+
91+ * jdk8 中主要做了 2 方面的改进
92+ * 取消 segments 字段,直接采用 ` transient volatile HashEntry<K,V>[] table ` 保存数据,采用 table 数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
93+ * 将原先 table 数组+单向链表的数据结构,变更为 table 数组+单向链表+红黑树的结构。对于 hash 表来说,最核心的能力在于将 key hash 之后能均匀的分布在数组中。如果 hash 之后散列的很均匀,那么 table 数组中的每个队列长度主要为 0 或者 1。但实际情况并非总是如此理想,虽然 ConcurrentHashMap 类默认的加载因子为 0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为 O(n);因此,对于个数超过 8(默认值)的列表,jdk1.8 中采用了红黑树的结构,那么查询的时间复杂度可以降低到 O(logN),可以改进性能。
94+
95+ ``` java
96+ final V putVal(K key, V value, boolean onlyIfAbsent) {
97+ if (key == null || value == null ) throw new NullPointerException ();
98+ int hash = spread(key. hashCode());
99+ int binCount = 0 ;
100+ for (Node<K ,V > [] tab = table;;) {
101+ Node<K ,V > f; int n, i, fh;
102+ // 如果table为空,初始化;否则,根据hash值计算得到数组索引i,如果tab[i]为空,直接新建节点Node即可。注:tab[i]实质为链表或者红黑树的首节点。
103+ if (tab == null || (n = tab. length) == 0 )
104+ tab = initTable();
105+ else if ((f = tabAt(tab, i = (n - 1 ) & hash)) == null ) {
106+ if (casTabAt(tab, i, null ,
107+ new Node<K ,V > (hash, key, value, null )))
108+ break ; // no lock when adding to empty bin
109+ }
110+ // 如果tab[i]不为空并且hash值为MOVED,说明该链表正在进行transfer操作,返回扩容完成后的table。
111+ else if ((fh = f. hash) == MOVED )
112+ tab = helpTransfer(tab, f);
113+ else {
114+ V oldVal = null ;
115+ // 针对首个节点进行加锁操作,而不是segment,进一步减少线程冲突
116+ synchronized (f) {
117+ if (tabAt(tab, i) == f) {
118+ if (fh >= 0 ) {
119+ binCount = 1 ;
120+ for (Node<K ,V > e = f;; ++ binCount) {
121+ K ek;
122+ // 如果在链表中找到值为key的节点e,直接设置e.val = value即可。
123+ if (e. hash == hash &&
124+ ((ek = e. key) == key ||
125+ (ek != null && key. equals(ek)))) {
126+ oldVal = e. val;
127+ if (! onlyIfAbsent)
128+ e. val = value;
129+ break ;
130+ }
131+ // 如果没有找到值为key的节点,直接新建Node并加入链表即可。
132+ Node<K ,V > pred = e;
133+ if ((e = e. next) == null ) {
134+ pred. next = new Node<K ,V > (hash, key,
135+ value, null );
136+ break ;
137+ }
138+ }
139+ }
140+ // 如果首节点为TreeBin类型,说明为红黑树结构,执行putTreeVal操作。
141+ else if (f instanceof TreeBin ) {
142+ Node<K ,V > p;
143+ binCount = 2 ;
144+ if ((p = ((TreeBin<K ,V > )f). putTreeVal(hash, key,
145+ value)) != null ) {
146+ oldVal = p. val;
147+ if (! onlyIfAbsent)
148+ p. val = value;
149+ }
150+ }
151+ }
152+ }
153+ if (binCount != 0 ) {
154+ // 如果节点数>=8,那么转换链表结构为红黑树结构。
155+ if (binCount >= TREEIFY_THRESHOLD )
156+ treeifyBin(tab, i);
157+ if (oldVal != null )
158+ return oldVal;
159+ break ;
160+ }
161+ }
162+ }
163+ // 计数增加1,有可能触发transfer操作(扩容)。
164+ addCount(1L , binCount);
165+ return null ;
166+ }
167+ ```
168+
169+ ### 示例
170+
171+ ``` java
172+ public class ConcurrentHashMapDemo {
173+
174+ public static void main (String [] args ) throws InterruptedException {
175+
176+ // HashMap 在并发迭代访问时会抛出 ConcurrentModificationException 异常
177+ // Map<Integer, Character> map = new HashMap<>();
178+ Map<Integer , Character > map = new ConcurrentHashMap<> ();
179+
180+ Thread wthread = new Thread (() - > {
181+ System . out. println(" 写操作线程开始执行" );
182+ for (int i = 0 ; i < 26 ; i++ ) {
183+ map. put(i, (char ) (' a' + i));
184+ }
185+ });
186+ Thread rthread = new Thread (() - > {
187+ System . out. println(" 读操作线程开始执行" );
188+ for (Integer key : map. keySet()) {
189+ System . out. println(key + " - " + map. get(key));
190+ }
191+ });
192+ wthread. start();
193+ rthread. start();
194+ Thread . sleep(1000 );
195+ }
196+ }
197+ ```
46198
47199## CopyOnWriteArrayList
48200
201+ ### 要点
202+
49203* 作用:CopyOnWrite 字面意思为写入时复制。CopyOnWriteArrayList 是线程安全的 ArrayList。
50- * 原理:写入时复制(CopyOnWrite)容器的迭代器保留一个指向底层基础数组的引用,这个数组当前位于迭代器的起始位置,由于它不会被修改,因此在对其进行同步时只需确保数组内容的可见性。
204+ * 原理:
205+ * 在 CopyOnWriteAarrayList 中,读操作不同步,因为它们在内部数组的快照上工作,所以多个迭代器可以同时遍历而不会相互阻塞(1,2,4)。
206+ * 所有的写操作都是同步的。他们在备份数组(3)的副本上工作。写操作完成后,后备阵列将被替换为复制的阵列,并释放锁定。支持数组变得易变,所以替换数组的调用是原子(5)。
207+ * 写操作后创建的迭代器将能够看到修改的结构(6,7)。
208+ * 写时复制集合返回的迭代器不会抛出 ConcurrentModificationException,因为它们在数组的快照上工作,并且无论后续的修改(2,4)如何,都会像迭代器创建时那样完全返回元素。
209+
210+ <p align =" center " >
211+ <img src =" https://raw.githubusercontent.com/dunwu/javase-notes/master/images/concurrent/CopyOnWriteArrayList.png " >
212+ </p >
213+
214+ ### 源码
215+
216+ #### 重要属性
217+
218+ * lock - 执行写时复制操作,需要使用可重入锁加锁
219+ * array - 对象数组,用于存放元素
220+
221+ ``` java
222+ /* * The lock protecting all mutators */
223+ final transient ReentrantLock lock = new ReentrantLock ();
224+
225+ /* * The array, accessed only via getArray/setArray. */
226+ private transient volatile Object [] array;
227+ ```
228+
229+ #### 重要方法
230+
231+ * 添加操作
232+ * 添加的逻辑很简单,先将原容器 copy 一份,然后在新副本上执行写操作,之后再切换引用。当然此过程是要加锁的。
233+
234+ ``` java
235+ public boolean add(E e) {
236+ // ReentrantLock加锁,保证线程安全
237+ final ReentrantLock lock = this . lock;
238+ lock. lock();
239+ try {
240+ Object [] elements = getArray();
241+ int len = elements. length;
242+ // 拷贝原容器,长度为原容器长度加一
243+ Object [] newElements = Arrays . copyOf(elements, len + 1 );
244+ // 在新副本上执行添加操作
245+ newElements[len] = e;
246+ // 将原容器引用指向新副本
247+ setArray(newElements);
248+ return true ;
249+ } finally {
250+ // 解锁
251+ lock. unlock();
252+ }
253+ }
254+ ```
255+
256+ * 删除操作
257+ * 删除操作同理,将除要删除元素之外的其他元素拷贝到新副本中,然后切换引用,将原容器引用指向新副本。同属写操作,需要加锁。
258+
259+ ``` java
260+ public E remove(int index) {
261+ // 加锁
262+ final ReentrantLock lock = this . lock;
263+ lock. lock();
264+ try {
265+ Object [] elements = getArray();
266+ int len = elements. length;
267+ E oldValue = get(elements, index);
268+ int numMoved = len - index - 1 ;
269+ if (numMoved == 0 )
270+ // 如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
271+ setArray(Arrays . copyOf(elements, len - 1 ));
272+ else {
273+ // 否则,将除要删除元素之外的其他元素拷贝到新副本中,并切换引用
274+ Object [] newElements = new Object [len - 1 ];
275+ System . arraycopy(elements, 0 , newElements, 0 , index);
276+ System . arraycopy(elements, index + 1 , newElements, index,
277+ numMoved);
278+ setArray(newElements);
279+ }
280+ return oldValue;
281+ } finally {
282+ // 解锁
283+ lock. unlock();
284+ }
285+ }
286+ ```
287+
288+ * 读操作
289+ * CopyOnWriteArrayList 的读操作是不用加锁的,性能很高。
290+
291+ ``` java
292+ public E get(int index) {
293+ return get(getArray(), index);
294+ }
295+ private E get(Object [] a, int index) {
296+ return (E ) a[index];
297+ }
298+ ```
299+
300+ ### 示例
301+
302+ ``` java
303+ public class CopyOnWriteArrayListDemo {
304+
305+ static class ReadTask implements Runnable {
306+
307+ List<String > list;
308+
309+ ReadTask (List<String > list ) {
310+ this . list = list;
311+ }
312+
313+ public void run () {
314+ for (String str : list) {
315+ System . out. println(str);
316+ }
317+ }
318+ }
319+
320+ static class WriteTask implements Runnable {
321+
322+ List<String > list;
323+ int index;
324+
325+ WriteTask (List<String > list , int index ) {
326+ this . list = list;
327+ this . index = index;
328+ }
329+
330+ public void run () {
331+ list. remove(index);
332+ list. add(index, " write_" + index);
333+ }
334+ }
335+
336+ public void run () {
337+ final int NUM = 10 ;
338+ // ArrayList 在并发迭代访问时会抛出 ConcurrentModificationException 异常
339+ // List<String> list = new ArrayList<>();
340+ CopyOnWriteArrayList<String > list = new CopyOnWriteArrayList<> ();
341+ for (int i = 0 ; i < NUM ; i++ ) {
342+ list. add(" main_" + i);
343+ }
344+ ExecutorService executorService = Executors . newFixedThreadPool(NUM );
345+ for (int i = 0 ; i < NUM ; i++ ) {
346+ executorService. execute(new ReadTask (list));
347+ executorService. execute(new WriteTask (list, i));
348+ }
349+ executorService. shutdown();
350+ }
351+
352+ public static void main (String [] args ) {
353+ new CopyOnWriteArrayListDemo (). run();
354+ }
355+ }
356+ ```
51357
52358## 资料
53359
54360* [ Java 并发编程实战] ( https://item.jd.com/10922250.html )
55361* [ Java 并发编程的艺术] ( https://item.jd.com/11740734.html )
56362* https://blog.csdn.net/u010425776/article/details/54890215
363+ * https://blog.csdn.net/wangxiaotongfan/article/details/52074160
364+ * https://my.oschina.net/hosee/blog/675884
365+ * https://www.jianshu.com/p/c0642afe03e0
366+ * https://www.jianshu.com/p/f6730d5784ad
367+ * http://www.javarticles.com/2012/06/copyonwritearraylist.html
368+ * https://www.cnblogs.com/xrq730/p/5020760.html
369+ * https://www.cnblogs.com/leesf456/p/5547853.html
370+ * http://www.cnblogs.com/chengxiao/p/6881974.html
0 commit comments