java 的 CopyOnWriteArrayList类
初識CopyOnWriteArrayList
第一次見到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 | public class Driver extends NonRegisteringDriver ??implements java.sql.Driver { ??public Driver() ????throws SQLException ??{ ??} ??static ??{ ????try ????{ ??????DriverManager.registerDriver(new Driver()); ????} catch (SQLException E) { ??????throw new RuntimeException("Can't register driver!"); ????} ??} } |
看到com.mysql.jdbc.Driver調用了DriverManager的registerDriver方法,這個類在java.sql.DriverManager下:
| 1 2 3 4 5 6 7 8 9 10 11 12 | public class DriverManager { ????private static final CopyOnWriteArrayList<DriverInfo> ????registeredDrivers = new CopyOnWriteArrayList(); ????private static volatile int loginTimeout = 0; ????private static volatile PrintWriter logWriter = null; ????private static volatile PrintStream logStream = null; ????private static final Object logSync = new Object(); ????static final SQLPermission SET_LOG_PERMISSION = new ????SQLPermission("setLog"); ????... } |
看到所有的DriverInfo都在CopyOnWriteArrayList中。既然看到了CopyOnWriteArrayList,我自然免不了要研究一番為什么JDK使用的是這個List。
首先提兩點:
1、CopyOnWriteArrayList位于java.util.concurrent包下,可想而知,這個類是為并發而設計的
2、CopyOnWriteArrayList,顧名思義,Write的時候總是要Copy,也就是說對于CopyOnWriteArrayList,任何可變的操作(add、set、remove等等)都是伴隨復制這個動作的,后面會解讀CopyOnWriteArrayList的底層實現機制
四個關注點在CopyOnWriteArrayList上的答案
如何向CopyOnWriteArrayList中添加元素
對于CopyOnWriteArrayList來說,增加、刪除、修改、插入的原理都是一樣的,所以用增加元素來分析一下CopyOnWriteArrayList的底層實現機制就可以了。先看一段代碼:
| 1 2 3 4 5 6 | public static void main(String[] args) { ?????List<Integer> list = new CopyOnWriteArrayList<Integer>(); ?????list.add(1); ?????list.add(2); } |
看一下這段代碼做了什么,先是第3行的實例化一個新的CopyOnWriteArrayList:
| 1 2 3 4 5 6 7 8 9 10 11 | public class CopyOnWriteArrayList<E> ????implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ????private static final long serialVersionUID = 8673264195747942595L; ????/** The lock protecting all mutators */ ????transient final ReentrantLock lock = new ReentrantLock(); ????/** The array, accessed only via getArray/setArray. */ ????private volatile transient Object[] array; ????... } |
| 1 2 3 | public CopyOnWriteArrayList() { ????setArray(new Object[0]); } |
| 1 2 3 | final void setArray(Object[] a) { ????array = a; } |
看到,對于CopyOnWriteArrayList來說,底層就是一個Object[] array,然后實例化一個CopyOnWriteArrayList,用圖來表示非常簡單:
就是這樣,Object array指向一個數組大小為0的數組。接著看一下,第4行的add一個整數1做了什么,add的源代碼是:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { ????Object[] elements = getArray(); ????int len = elements.length; ????Object[] newElements = Arrays.copyOf(elements, len + 1); ????newElements[len] = e; ????setArray(newElements); ????return true; } finally { ????lock.unlock(); } } |
畫一張圖表示一下:
每一步都清楚地表示在圖上了,一次add大致經歷了幾個步驟:
1、加鎖
2、拿到原數組,得到新數組的大小(原數組大小+1),實例化出一個新的數組來
3、把原數組的元素復制到新數組中去
4、新數組最后一個位置設置為待添加的元素(因為新數組的大小是按照原數組大小+1來的)
5、把Object array引用指向新數組
6、解鎖
整個過程看起來比較像ArrayList的擴容。有了這個基礎,我們再來看一下第5行的add了一個整數2做了什么,這應該非常簡單了,還是畫一張圖來表示:
和前面差不多,就不解釋了。
另外,插入、刪除、修改操作也都是一樣,每一次的操作都是以對Object[] array進行一次復制為基礎的,如果上面的流程看懂了,那么研究插入、刪除、修改的源代碼應該不難。
普通List的缺陷
常用的List有ArrayList、LinkedList、Vector,其中前兩個是線程非安全的,最后一個是線程安全的。我有一種場景,兩個線程操作了同一個List,分別對同一個List進行迭代和刪除,就如同下面的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public static class T1 extends Thread { ????private List<Integer> list; ????public T1(List<Integer> list) ????{ ????????this.list = list; ????} ????public void run() ????{ ????????for (Integer i : list) ????????{ ????????} ????} } public static class T2 extends Thread { ????private List<Integer> list; ????public T2(List<Integer> list) ????{ ????????this.list = list; ????} ????public void run() ????{ ????????for (int i = 0; i < list.size(); i++) ????????{ ????????????list.remove(i); ????????} ????} } |
首先我在這兩個線程中放入ArrayList并啟動這兩個線程:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void main(String[] args) { ????List<Integer> list = new ArrayList<Integer>(); ????for (int i = 0; i < 10000; i++) ????{ ????????list.add(i); ????} ????T1 t1 = new T1(list); ????T2 t2 = new T2(list); ????t1.start(); ????t2.start(); } |
運行結果為:
| 1 2 3 4 | Exception in thread "Thread-0" java.util.ConcurrentModificationException ????at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372) ????at java.util.AbstractList$Itr.next(AbstractList.java:343) ????at com.xrq.test60.TestMain$T1.run(TestMain.java:19) |
把ArrayList換成LinkedList,main函數的代碼就不貼了,運行結果為:
| 1 2 3 4 | Exception in thread "Thread-0" java.util.ConcurrentModificationException ????at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:761) ????at java.util.LinkedList$ListItr.next(LinkedList.java:696) ????at com.xrq.test60.TestMain$T1.run(TestMain.java:19) |
可能有人覺得,這兩個線程都是線程非安全的類,所以不行。其實這個問題和線程安不安全沒有關系,換成Vector看一下運行結果:
| 1 2 3 4 | Exception in thread "Thread-0" java.util.ConcurrentModificationException ????at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372) ????at java.util.AbstractList$Itr.next(AbstractList.java:343) ????at com.xrq.test60.TestMain$T1.run(TestMain.java:19) |
Vector雖然是線程安全的,但是只是一種相對的線程安全而不是絕對的線程安全,它只能夠保證增、刪、改、查的單個操作一定是原子的,不會被打斷,但是如果組合起來用,并不能保證線程安全性。比如就像上面的線程1在遍歷一個Vector中的元素、線程2在刪除一個Vector中的元素一樣,勢必產生并發修改異常,也就是fail-fast。
CopyOnWriteArrayList的作用
把上面的代碼修改一下,用CopyOnWriteArrayList:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void main(String[] args) { ????List<Integer> list = new CopyOnWriteArrayList<Integer>(); ????for (int i = 0; i < 10; i++) ????{ ????????list.add(i); ????} ????T1 t1 = new T1(list); ????T2 t2 = new T2(list); ????t1.start(); ????t2.start(); } |
可以運行一下這段代碼,是沒有任何問題的。
看到我把元素數量改小了一點,因為我們從上面的分析中應該可以看出,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適用于讀操作遠多于修改操作的并發場景中。
?
?
本文轉載于:?http://www.importnew.com/25034.html
?
?
?
結尾貼上兩個我測試的Demo 示例:
測試一:
package com.zslin.list.demo;import java.util.ArrayList; import java.util.List;/*** * @author WQ<br>* @version 創建時間:2017年6月18日 下午4:15:54<br>*/ public class Resource3 {public static void main(String[] args) throws InterruptedException {List<String> a = new ArrayList<String>();a.add("a");a.add("b");a.add("c");final ArrayList<String> list = new ArrayList<String>(a);Thread t = new Thread(new Runnable() {int count = -1;@Overridepublic void run() {while (true) {list.add(count++ + "");}}});t.setDaemon(true);t.start();Thread.currentThread().sleep(3);for (String s : list) {System.out.println(s);}} }運行結果:
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.zslin.list.demo.Resource3.main(Resource3.java:31)?
?
測試二:
package com.zslin.list.demo;import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList;/*** * @author WQ<br>* @version 創建時間:2017年6月18日 下午4:17:48<br>*/ public class Resource4 {public static void main(String[] args) throws InterruptedException {List<String> a = new ArrayList<String>();a.add("a");a.add("b");a.add("c");final CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(a);Thread t = new Thread(new Runnable() {int count = -1;@Overridepublic void run() {while (true) {list.add(count++ + "");}}});t.setDaemon(true);t.start();Thread.currentThread().sleep(3);for (String s : list) {System.out.println(list.hashCode());System.out.println(s);}} }運行結果:
159947112 a 1157371761 b -478062346 c -998300255 -1 -1122793921 0 1172437517 1 1152826799 2 -1744105465。。。。。。。。。//省略部分運行結果?
轉載于:https://www.cnblogs.com/mr-wuxiansheng/p/7044571.html
總結
以上是生活随笔為你收集整理的java 的 CopyOnWriteArrayList类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 长沙银行信用卡电话多少?打电话要收费吗?
- 下一篇: 更新 hadoop eclipse 插件