第一次見到CopyOnWriteArrayList,是在研究JDBC的時候,每一個資料庫的Driver都是維護在一個CopyOnWriteArrayList中的,為了證明這一點,貼兩段代碼,第一段在com.mysql.jdbc.Driver下,也就是我們寫Class.forName(“…”)中的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<code>public</code> <code>class</code> <code>Driver</code><code>extends</code> <code>NonRegisteringDriver</code>
<code> </code><code>implements</code> <code>java.sql.Driver</code>
<code>{</code>
<code> </code><code>public</code> <code>Driver()</code>
<code> </code><code>throws</code> <code>SQLException</code>
<code> </code><code>{</code>
<code> </code><code>}</code>
<code> </code><code>static</code>
<code> </code><code>try</code>
<code> </code><code>{</code>
<code> </code><code>DriverManager.registerDriver(</code><code>new</code> <code>Driver());</code>
<code> </code><code>}</code><code>catch</code> <code>(SQLException E) {</code>
<code> </code><code>throw</code> <code>new</code> <code>RuntimeException(</code><code>"Can't register driver!"</code><code>);</code>
<code> </code><code>}</code>
<code>}</code>
看到com.mysql.jdbc.Driver調用了DriverManager的registerDriver方法,這個類在java.sql.DriverManager下:
<code>public</code> <code>class</code> <code>DriverManager</code>
<code> </code><code>private</code> <code>static</code> <code>final</code> <code>CopyOnWriteArrayList<DriverInfo></code>
<code> </code><code>registeredDrivers =</code><code>new</code> <code>CopyOnWriteArrayList();</code>
<code> </code><code>private</code> <code>static</code> <code>volatile</code> <code>int</code> <code>loginTimeout =</code><code>0</code><code>;</code>
<code> </code><code>private</code> <code>static</code> <code>volatile</code> <code>PrintWriter logWriter =</code><code>null</code><code>;</code>
<code> </code><code>private</code> <code>static</code> <code>volatile</code> <code>PrintStream logStream =</code><code>null</code><code>;</code>
<code> </code><code>private</code> <code>static</code> <code>final</code> <code>Object logSync =</code><code>new</code> <code>Object();</code>
<code> </code><code>static</code> <code>final</code> <code>SQLPermission SET_LOG_PERMISSION =</code><code>new</code>
<code> </code><code>SQLPermission(</code><code>"setLog"</code><code>);</code>
<code> </code><code>...</code>
看到所有的DriverInfo都在CopyOnWriteArrayList中。既然看到了CopyOnWriteArrayList,我自然免不了要研究一番為什麼JDK使用的是這個List。
首先提兩點:
1、CopyOnWriteArrayList位于java.util.concurrent包下,可想而知,這個類是為并發而設計的
2、CopyOnWriteArrayList,顧名思義,Write的時候總是要Copy,也就是說對于CopyOnWriteArrayList,任何可變的操作(add、set、remove等等)都是伴随複制這個動作的,後面會解讀CopyOnWriteArrayList的底層實作機制
<a href="http://www.importnew.com/25034.html/qq20170610-2355222x"></a>
對于CopyOnWriteArrayList來說,增加、删除、修改、插入的原理都是一樣的,是以用增加元素來分析一下CopyOnWriteArrayList的底層實作機制就可以了。先看一段代碼:
<code>public</code> <code>static</code> <code>void</code> <code>main(String[] args)</code>
<code> </code><code>List<Integer> list =</code><code>new</code> <code>CopyOnWriteArrayList<Integer>();</code>
<code> </code><code>list.add(</code><code>1</code><code>);</code>
<code> </code><code>list.add(</code><code>2</code><code>);</code>
看一下這段代碼做了什麼,先是第3行的執行個體化一個新的CopyOnWriteArrayList:
<code>public</code> <code>class</code> <code>CopyOnWriteArrayList<E></code>
<code> </code><code>implements</code> <code>List<E>, RandomAccess, Cloneable, java.io.Serializable {</code>
<code> </code><code>private</code> <code>static</code> <code>final</code> <code>long</code> <code>serialVersionUID = 8673264195747942595L;</code>
<code> </code><code>/** The lock protecting all mutators */</code>
<code> </code><code>transient</code> <code>final</code> <code>ReentrantLock lock =</code><code>new</code> <code>ReentrantLock();</code>
<code> </code><code>/** The array, accessed only via getArray/setArray. */</code>
<code> </code><code>private</code> <code>volatile</code> <code>transient</code> <code>Object[] array;</code>
<code>public</code> <code>CopyOnWriteArrayList() {</code>
<code> </code><code>setArray(</code><code>new</code> <code>Object[</code><code>0</code><code>]);</code>
<code>final</code> <code>void</code> <code>setArray(Object[] a) {</code>
<code> </code><code>array = a;</code>
看到,對于CopyOnWriteArrayList來說,底層就是一個Object[] array,然後執行個體化一個CopyOnWriteArrayList,用圖來表示非常簡單:
<a href="http://www.importnew.com/25034.html/801753-20151206204140237-916405541"></a>
就是這樣,Object array指向一個數組大小為0的數組。接着看一下,第4行的add一個整數1做了什麼,add的源代碼是:
<code>public</code> <code>boolean</code> <code>add(E e) {</code>
<code>final</code> <code>ReentrantLock lock =</code><code>this</code><code>.lock;</code>
<code>lock.lock();</code>
<code>try</code> <code>{</code>
<code> </code><code>Object[] elements = getArray();</code>
<code> </code><code>int</code> <code>len = elements.length;</code>
<code> </code><code>Object[] newElements = Arrays.copyOf(elements, len +</code><code>1</code><code>);</code>
<code> </code><code>newElements[len] = e;</code>
<code> </code><code>setArray(newElements);</code>
<code> </code><code>return</code> <code>true</code><code>;</code>
<code>}</code><code>finally</code> <code>{</code>
<code> </code><code>lock.unlock();</code>
畫一張圖表示一下:
<a href="http://www.importnew.com/25034.html/801753-20151206205856722-1595736205"></a>
每一步都清楚地表示在圖上了,一次add大緻經曆了幾個步驟:
1、加鎖
2、拿到原數組,得到新數組的大小(原數組大小+1),執行個體化出一個新的數組來
3、把原數組的元素複制到新數組中去
4、新數組最後一個位置設定為待添加的元素(因為新數組的大小是按照原數組大小+1來的)
5、把Object array引用指向新數組
6、解鎖
整個過程看起來比較像ArrayList的擴容。有了這個基礎,我們再來看一下第5行的add了一個整數2做了什麼,這應該非常簡單了,還是畫一張圖來表示:
<a href="http://www.importnew.com/25034.html/801753-20151206211902831-1935187212"></a>
和前面差不多,就不解釋了。
另外,插入、删除、修改操作也都是一樣,每一次的操作都是以對Object[] array進行一次複制為基礎的,如果上面的流程看懂了,那麼研究插入、删除、修改的源代碼應該不難。
常用的List有ArrayList、LinkedList、Vector,其中前兩個是線程非安全的,最後一個是線程安全的。我有一種場景,兩個線程操作了同一個List,分别對同一個List進行疊代和删除,就如同下面的代碼:
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<code>public</code> <code>static</code> <code>class</code> <code>T1</code><code>extends</code> <code>Thread</code>
<code> </code><code>private</code> <code>List<Integer> list;</code>
<code> </code><code>public</code> <code>T1(List<Integer> list)</code>
<code> </code><code>this</code><code>.list = list;</code>
<code> </code><code>public</code> <code>void</code> <code>run()</code>
<code> </code><code>for</code> <code>(Integer i : list)</code>
<code> </code><code>{</code>
<code> </code><code>}</code>
<code>public</code> <code>static</code> <code>class</code> <code>T2</code><code>extends</code> <code>Thread</code>
<code> </code><code>public</code> <code>T2(List<Integer> list)</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>i =</code><code>0</code><code>; i < list.size(); i++)</code>
<code> </code><code>list.remove(i);</code>
首先我在這兩個線程中放入ArrayList并啟動這兩個線程:
<code> </code><code>List<Integer> list =</code><code>new</code> <code>ArrayList<Integer>();</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>i =</code><code>0</code><code>; i <</code><code>10000</code><code>; i++)</code>
<code> </code><code>list.add(i);</code>
<code> </code><code>T1 t1 =</code><code>new</code> <code>T1(list);</code>
<code> </code><code>T2 t2 =</code><code>new</code> <code>T2(list);</code>
<code> </code><code>t1.start();</code>
<code> </code><code>t2.start();</code>
運作結果為:
<code>Exception in thread</code><code>"Thread-0"</code> <code>java.util.ConcurrentModificationException</code>
<code> </code><code>at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:</code><code>372</code><code>)</code>
<code> </code><code>at java.util.AbstractList$Itr.next(AbstractList.java:</code><code>343</code><code>)</code>
<code> </code><code>at com.xrq.test60.TestMain$T1.run(TestMain.java:</code><code>19</code><code>)</code>
把ArrayList換成LinkedList,main函數的代碼就不貼了,運作結果為:
<code> </code><code>at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:</code><code>761</code><code>)</code>
<code> </code><code>at java.util.LinkedList$ListItr.next(LinkedList.java:</code><code>696</code><code>)</code>
可能有人覺得,這兩個線程都是線程非安全的類,是以不行。其實這個問題和線程安不安全沒有關系,換成Vector看一下運作結果:
Vector雖然是線程安全的,但是隻是一種相對的線程安全而不是絕對的線程安全,它隻能夠保證增、删、改、查的單個操作一定是原子的,不會被打斷,但是如果組合起來用,并不能保證線程安全性。比如就像上面的線程1在周遊一個Vector中的元素、線程2在删除一個Vector中的元素一樣,勢必産生并發修改異常,也就是fail-fast。
把上面的代碼修改一下,用CopyOnWriteArrayList:
<code> </code><code>List<Integer> list =</code><code>new</code> <code>CopyOnWriteArrayList<Integer>();</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>i =</code><code>0</code><code>; i <</code><code>10</code><code>; i++)</code>
可以運作一下這段代碼,是沒有任何問題的。
看到我把元素數量改小了一點,因為我們從上面的分析中應該可以看出,CopyOnWriteArrayList的缺點,就是修改代價十分昂貴,每次修改都伴随着一次的數組複制;但同時優點也十分明顯,就是在并發下不會産生任何的線程安全問題,也就是絕對的線程安全,這也是為什麼我們要使用CopyOnWriteArrayList的原因。
另外,有兩點必須講一下。我認為CopyOnWriteArrayList這個并發元件,其實反映的是兩個十分重要的分布式理念:
(1)讀寫分離
我們讀取CopyOnWriteArrayList的時候讀取的是CopyOnWriteArrayList中的Object[] array,但是修改的時候,操作的是一個新的Object[] array,讀和寫操作的不是同一個對象,這就是讀寫分離。這種技術資料庫用的非常多,在高并發下為了緩解資料庫的壓力,即使做了緩存也要對資料庫做讀寫分離,讀的時候使用讀庫,寫的時候使用寫庫,然後讀庫、寫庫之間進行一定的同步,這樣就避免同一個庫上讀、寫的IO操作太多
(2)最終一緻
對CopyOnWriteArrayList來說,線程1讀取集合裡面的資料,未必是最新的資料。因為線程2、線程3、線程4四個線程都修改了CopyOnWriteArrayList裡面的資料,但是線程1拿到的還是最老的那個Object[] array,新添加進去的資料并沒有,是以線程1讀取的内容未必準确。不過這些資料雖然對于線程1是不一緻的,但是對于之後的線程一定是一緻的,它們拿到的Object[] array一定是三個線程都操作完畢之後的Object array[],這就是最終一緻。最終一緻對于分布式系統也非常重要,它通過容忍一定時間的資料不一緻,提升整個分布式系統的可用性與分區容錯性。當然,最終一緻并不是任何場景都适用的,像火車站售票這種系統使用者對于資料的實時性要求非常非常高,就必須做成強一緻性的。
最後總結一點,随着CopyOnWriteArrayList中元素的增加,CopyOnWriteArrayList的修改代價将越來越昂貴,是以,CopyOnWriteArrayList适用于讀操作遠多于修改操作的并發場景中。
結尾貼上兩個我測試的Demo 示例:
測試一:
運作結果:
測試二: