国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

圖解集合3:CopyOnWriteArrayList

2019-11-14 14:53:40
字體:
供稿:網(wǎng)友

初識CopyOnWriteArrayList

第一次見到CopyOnWriteArrayList,是在研究JDBC的時(shí)候,每一個(gè)數(shù)據(jù)庫的Driver都是維護(hù)在一個(gè)CopyOnWriteArrayList中的,為了證明這一點(diǎn),貼兩段代碼,第一段在com.MySQL.jdbc.Driver下,也就是我們寫Class.forName("...")中的內(nèi)容:

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調(diào)用了DriverManager的registerDriver方法,這個(gè)類在java.sql.DriverManager下:

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使用的是這個(gè)List。

首先提兩點(diǎn):

1、CopyOnWriteArrayList位于java.util.concurrent包下,可想而知,這個(gè)類是為并發(fā)而設(shè)計(jì)的

2、CopyOnWriteArrayList,顧名思義,Write的時(shí)候總是要Copy,也就是說對于CopyOnWriteArrayList,任何可變的操作(add、set、remove等等)都是伴隨復(fù)制這個(gè)動作的,后面會解讀CopyOnWriteArrayList的底層實(shí)現(xiàn)機(jī)制

 

四個(gè)關(guān)注點(diǎn)在CopyOnWriteArrayList上的答案

關(guān)  注  點(diǎn)結(jié)      論
CopyOnWriteArrayList是否允許空允許
CopyOnWriteArrayList是否允許重復(fù)數(shù)據(jù)允許
CopyOnWriteArrayList是否有序有序
CopyOnWriteArrayList是否線程安全線程安全

 

如何向CopyOnWriteArrayList中添加元素 

對于CopyOnWriteArrayList來說,增加、刪除、修改、插入的原理都是一樣的,所以用增加元素來分析一下CopyOnWriteArrayList的底層實(shí)現(xiàn)機(jī)制就可以了。先看一段代碼:

1 public static void main(String[] args)2 {3     List<Integer> list = new CopyOnWriteArrayList<Integer>();4     list.add(1);5     list.add(2);6 }

看一下這段代碼做了什么,先是第3行的實(shí)例化一個(gè)新的CopyOnWriteArrayList:

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;    ...}
public CopyOnWriteArrayList() {    setArray(new Object[0]);}
final void setArray(Object[] a) {    array = a;}

看到,對于CopyOnWriteArrayList來說,底層就是一個(gè)Object[] array,然后實(shí)例化一個(gè)CopyOnWriteArrayList,用圖來表示非常簡單:

就是這樣,Object array指向一個(gè)數(shù)組大小為0的數(shù)組。接著看一下,第4行的add一個(gè)整數(shù)1做了什么,add的源代碼是:

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大致經(jīng)歷了幾個(gè)步驟:

1、加鎖

2、拿到原數(shù)組,得到新數(shù)組的大小(原數(shù)組大小+1),實(shí)例化出一個(gè)新的數(shù)組來

3、把原數(shù)組的元素復(fù)制到新數(shù)組中去

4、新數(shù)組最后一個(gè)位置設(shè)置為待添加的元素(因?yàn)樾聰?shù)組的大小是按照原數(shù)組大小+1來的)

5、把Object array引用指向新數(shù)組

6、解鎖

整個(gè)過程看起來比較像ArrayList的擴(kuò)容。有了這個(gè)基礎(chǔ),我們再來看一下第5行的add了一個(gè)整數(shù)2做了什么,這應(yīng)該非常簡單了,還是畫一張圖來表示:

和前面差不多,就不解釋了。

另外,插入、刪除、修改操作也都是一樣,每一次的操作都是以對Object[] array進(jìn)行一次復(fù)制為基礎(chǔ)的,如果上面的流程看懂了,那么研究插入、刪除、修改的源代碼應(yīng)該不難。 

 

普通List的缺陷

常用的List有ArrayList、LinkedList、Vector,其中前兩個(gè)是線程非安全的,最后一個(gè)是線程安全的。我有一種場景,兩個(gè)線程操作了同一個(gè)List,分別對同一個(gè)List進(jìn)行迭代和刪除,就如同下面的代碼:

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);        }    }}

首先我在這兩個(gè)線程中放入ArrayList并啟動這兩個(gè)線程:

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();}

運(yùn)行結(jié)果為:

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函數(shù)的代碼就不貼了,運(yùn)行結(jié)果為:

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)

可能有人覺得,這兩個(gè)線程都是線程非安全的類,所以不行。其實(shí)這個(gè)問題和線程安不安全沒有關(guān)系,換成Vector看一下運(yùn)行結(jié)果:

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雖然是線程安全的,但是只是一種相對的線程安全而不是絕對的線程安全,它只能夠保證增、刪、改、查的單個(gè)操作一定是原子的,不會被打斷,但是如果組合起來用,并不能保證線程安全性。比如就像上面的線程1在遍歷一個(gè)Vector中的元素、線程2在刪除一個(gè)Vector中的元素一樣,勢必產(chǎn)生并發(fā)修改異常,也就是fail-fast

 

CopyOnWriteArrayList的作用

把上面的代碼修改一下,用CopyOnWriteArrayList:

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();}

可以運(yùn)行一下這段代碼,是沒有任何問題的。

看到我把元素?cái)?shù)量改小了一點(diǎn),因?yàn)槲覀儚纳厦娴姆治鲋袘?yīng)該可以看出,CopyOnWriteArrayList的缺點(diǎn),就是修改代價(jià)十分昂貴,每次修改都伴隨著一次的數(shù)組復(fù)制;但同時(shí)優(yōu)點(diǎn)也十分明顯,就是在并發(fā)下不會產(chǎn)生任何的線程安全問題,也就是絕對的線程安全,這也是為什么我們要使用CopyOnWriteArrayList的原因。

另外,有兩點(diǎn)必須講一下。我認(rèn)為CopyOnWriteArrayList這個(gè)并發(fā)組件,其實(shí)反映的是兩個(gè)十分重要的分布式理念:

(1)讀寫分離

我們讀取CopyOnWriteArrayList的時(shí)候讀取的是CopyOnWriteArrayList中的Object[] array,但是修改的時(shí)候,操作的是一個(gè)新的Object[] array,讀和寫操作的不是同一個(gè)對象,這就是讀寫分離。這種技術(shù)數(shù)據(jù)庫用的非常多,在高并發(fā)下為了緩解數(shù)據(jù)庫的壓力,即使做了緩存也要對數(shù)據(jù)庫做讀寫分離,讀的時(shí)候使用讀庫,寫的時(shí)候使用寫庫,然后讀庫、寫庫之間進(jìn)行一定的同步,這樣就避免同一個(gè)庫上讀、寫的IO操作太多

(2)最終一致

對CopyOnWriteArrayList來說,線程1讀取集合里面的數(shù)據(jù),未必是最新的數(shù)據(jù)。因?yàn)榫€程2、線程3、線程4四個(gè)線程都修改了CopyOnWriteArrayList里面的數(shù)據(jù),但是線程1拿到的還是最老的那個(gè)Object[] array,新添加進(jìn)去的數(shù)據(jù)并沒有,所以線程1讀取的內(nèi)容未必準(zhǔn)確。不過這些數(shù)據(jù)雖然對于線程1是不一致的,但是對于之后的線程一定是一致的,它們拿到的Object[] array一定是三個(gè)線程都操作完畢之后的Object array[],這就是最終一致。最終一致對于分布式系統(tǒng)也非常重要,它通過容忍一定時(shí)間的數(shù)據(jù)不一致,提升整個(gè)分布式系統(tǒng)的可用性與分區(qū)容錯(cuò)性。當(dāng)然,最終一致并不是任何場景都適用的,像火車站售票這種系統(tǒng)用戶對于數(shù)據(jù)的實(shí)時(shí)性要求非常非常高,就必須做成強(qiáng)一致性的。

最后總結(jié)一點(diǎn),隨著CopyOnWriteArrayList中元素的增加,CopyOnWriteArrayList的修改代價(jià)將越來越昂貴,因此,CopyOnWriteArrayList適用于讀操作遠(yuǎn)多于修改操作的并發(fā)場景中

 

 


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 平和县| 阳信县| 鹿邑县| 曲周县| 沙洋县| 东丽区| 礼泉县| 项城市| 大荔县| 武威市| 康保县| 建始县| 荣成市| 宿迁市| 孙吴县| 泰和县| 镇江市| 鸡东县| 正镶白旗| 新巴尔虎右旗| 西乌珠穆沁旗| 永泰县| 深州市| 司法| 徐汇区| 罗田县| 财经| 漳平市| 中山市| 阿克| 溧水县| 铜山县| 保亭| 凉山| 左贡县| 竹山县| 本溪市| 溆浦县| 沙田区| 通化市| 深圳市|