CopyOnWriteArrayList

今天來(lái)復(fù)習(xí)一下集合智绸。在支持并發(fā)的集合中达吞,我覺得CopyOnWriteArrayList是相對(duì)容易理解的一個(gè)昵观。
CopyOnWrite:寫時(shí)復(fù)制蜂奸,就是當(dāng)有線程向集合添加元素時(shí),不是直接往舊的容器中添加元素嚷节,而是將舊的容器中的元素復(fù)制到新的容器中聂儒,讀的時(shí)候讀的仍然是舊容器,這樣就不會(huì)影響并發(fā)讀了
分析一下CopyOnWriteArrayList部分源碼
1.add(E e)方法丹喻,向容器中添加元素

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

在往容器中加元素的過程是加鎖的,加鎖是通過可重入鎖ReentrantLock實(shí)現(xiàn)的翁都,如果不加鎖的話碍论,多個(gè)線程同時(shí)添加元素會(huì)復(fù)制多次。
getArray()獲得舊容器中的元素

final Object[] getArray() {
        return array;
    }

Arrays.copyOf(elements, len + 1)進(jìn)行數(shù)組的復(fù)制柄慰,并返回復(fù)制以后新的數(shù)組
newElements[len] = e向新的集合中添加元素
setArray(newElements)將舊容器的引用指向新容器

 final void setArray(Object[] a) {
        array = a;
    }

其余向容器中添加元素的方法鳍悠,比如public void add(int index, E element)實(shí)現(xiàn)思路和add(E e)大抵相同
2.get(int index),從容器中獲得指定位置的元素

public E get(int index) {
        return get(getArray(), index);
    }

實(shí)際調(diào)用的是get(Object[] a, int index)方法

private E get(Object[] a, int index) {
        return (E) a[index];
    }

得到指定位置的元素就是獲得數(shù)組中指定位置的元素
從代碼中可以看到,對(duì)于從容器中讀操作是不進(jìn)行加鎖的
3.remove(int index)坐搔,容器中移除元素

 public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //獲得原來(lái)舊的容器
            Object[] elements = getArray();
            int len = elements.length;
           //獲得指定位置上的元素
            E oldValue = get(elements, index);
           //移動(dòng)的距離
            int numMoved = len - index - 1;
           //如果集合中只有一個(gè)元素
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
               //將原來(lái)的舊容器的引用指向新的引用
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

實(shí)現(xiàn)的基本思想和add(E e)相同藏研,需要進(jìn)行加鎖,并且會(huì)對(duì)舊的容器進(jìn)行復(fù)制

4.看一個(gè)CopyOnWriteArrayList的構(gòu)造函數(shù)

public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

一般我們自己寫項(xiàng)目的時(shí)候都會(huì)選擇使用這個(gè)構(gòu)造函數(shù)概行。這個(gè)構(gòu)造函數(shù)并沒有初始化集合的大小蠢挡,集合大小為0,但是它沒有像ArrayList一樣有擴(kuò)容操作凳忙,因?yàn)樵谕@個(gè)容器中進(jìn)行寫操作時(shí)业踏,實(shí)際上并不是往當(dāng)前容器添加元素,而是會(huì)創(chuàng)建出一個(gè)比當(dāng)前容器大1的容器涧卵,在對(duì)往這個(gè)容器中添加元素勤家。所以不需要進(jìn)行擴(kuò)容×郑或者可以這么理解伐脖,它在每次添加元素的操作時(shí)都進(jìn)行了一次擴(kuò)容,每次擴(kuò)容一個(gè)元素的大小

5.CopyOnWriteArrayList的缺點(diǎn):
最明顯的一個(gè)致命缺點(diǎn)就是占大量的內(nèi)存乐设,在往容器中刪除元素和添加元素的時(shí)候都會(huì)在創(chuàng)建一個(gè)新的數(shù)組讼庇,如果垃圾收集器回收不及時(shí)的話,并且有很多線程進(jìn)行寫操作近尚,可能會(huì)撐爆內(nèi)存吧巫俺。
在一篇博客上看到CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性,不能保證數(shù)據(jù)的實(shí)時(shí)一致性肿男。所以如果你希望寫入的的數(shù)據(jù)介汹,馬上能讀到却嗡,請(qǐng)不要使用CopyOnWrite容器。不是特別理解這一點(diǎn)嘹承,volatile Object[] array窗价,array是有volatile修飾的,其保證了內(nèi)存的可見性叹卷,當(dāng)一個(gè)線程對(duì)一個(gè)共享變量的寫操作時(shí)撼港,寫完立刻就會(huì)對(duì)其他線程立即可見,那只要寫完骤竹,其他線程就能讀到新添加的值帝牡。自己寫了個(gè)demo進(jìn)行測(cè)試,感覺延遲效果不是很明顯
測(cè)試類:TestCopyOnWriteArrayList.java

public class TestCopyOnWriteArrayList {
    private static CopyOnWriteArrayList<String> c = new CopyOnWriteArrayList<>();
    private static long startTime;
    private static long endTime;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                c.add("a");
                startTime = System.currentTimeMillis();  //獲得添加a以后的時(shí)間
                System.out.println("添加了a");
                //添加了a以后讓其睡眠蒙揣,讓其他線程有時(shí)間執(zhí)行
                try {
                    Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.add("b");
                System.out.println("添加了b");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.add("c");
                System.out.println("添加了c");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.add("d");
                System.out.println("添加了d");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
//                System.out.println("讀取第一個(gè)元素");
                String s = c.get(0);
                endTime = System.currentTimeMillis();
                System.out.println("讀取到a花費(fèi)時(shí)間:" + (endTime - startTime) + "毫秒");
                System.out.println("s: " + s);
            }
        }).start();
    }
}

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

添加了a
讀取到a花費(fèi)時(shí)間:1毫秒
s: a
添加了b
添加了c
添加了d

1毫秒的延遲也不是特別長(zhǎng)吧
不知道是不是自己的例子不正確
6.CopyOnWriteArrayList的應(yīng)用場(chǎng)景:讀多寫少的場(chǎng)景

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末靶溜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子懒震,更是在濱河造成了極大的恐慌罩息,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件个扰,死亡現(xiàn)場(chǎng)離奇詭異瓷炮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)递宅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門娘香,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人办龄,你說我怎么就攤上這事茅主。” “怎么了土榴?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵诀姚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我玷禽,道長(zhǎng)赫段,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任矢赁,我火速辦了婚禮糯笙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撩银。我一直安慰自己给涕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著够庙,像睡著了一般恭应。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耘眨,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天昼榛,我揣著相機(jī)與錄音,去河邊找鬼剔难。 笑死胆屿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偶宫。 我是一名探鬼主播非迹,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纯趋!你這毒婦竟也來(lái)了憎兽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤结闸,失蹤者是張志新(化名)和其女友劉穎唇兑,沒想到半個(gè)月后酒朵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桦锄,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年蔫耽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了结耀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匙铡,死狀恐怖图甜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鳖眼,我是刑警寧澤黑毅,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站钦讳,受9級(jí)特大地震影響矿瘦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜愿卒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一缚去、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琼开,春花似錦易结、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)躏精。三九已至,卻和暖如春滋尉,著一層夾襖步出監(jiān)牢的瞬間玉控,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工狮惜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留高诺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓碾篡,卻偏偏與公主長(zhǎng)得像虱而,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子开泽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容