你知道Java中的CopyOnWriteArrayList嗎寥裂?

CopyOnWrite

  • CopyOnWrite是什么哈垢?
  • CopyOnWriteArrayList源碼分享?
  • CopyOnWriteArrayList使用場景惫霸?
  • CopyOnWriteArrayList有什么優(yōu)缺點猫缭?

如果你是求職者,你想想看怎么回答上面的問題它褪?

緣由

前段時間面試好多個人饵骨,問是否用過CopyOnWriteList,發(fā)現(xiàn)好多人都沒有用過翘悉,感覺挺驚訝的茫打。

CopyOnWrite看字面意思大概就可以明白了,copy集合之后再進(jìn)行write操作,我們也稱這個為寫時復(fù)制容器。

這個從 JDK 1.5版本就已經(jīng)有了老赤,Java并發(fā)包中有兩個實現(xiàn)這個機(jī)制的容器轮洋,分別是
CopyOnWriteArrayListCopyOnWriteArraySet

CopyOnWrite這個容器非常有用抬旺,特別是在并發(fā)的時候能夠提升效率弊予,很多并發(fā)的的場景中都可以用到CopyOnWrite的容器,我們在生產(chǎn)環(huán)境也用到過开财,今天托尼就和大家順便講講這個容器汉柒。

CopyOnWrite是什么

官方解釋
CopyOnWriteArrayList 是ArrayList的線程安全方式的一種方式。它的add责鳍、set方法底層實現(xiàn)都是重新復(fù)制一個新的容器來操作的碾褂。

CopyOnWriteArrayList 與ArrayList不同之處在于添加元素的時候會加上鎖。

CopyOnWriteArrayList在修改容器元素的時候并不是直接在原來的數(shù)組上進(jìn)行修改历葛,它是先拷貝一份正塌,然后在拷貝的數(shù)組上進(jìn)行修改,在修改完成之后將引用賦給原來的數(shù)組恤溶。

CopyOnWriteArrayList源碼分享

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
  • 實現(xiàn)了List接口乓诽,List的一種實現(xiàn)方式
  • 實現(xiàn)RandomAccess接口,看名稱就知道隨機(jī)訪問咒程,和數(shù)組訪問一樣根據(jù)下標(biāo)
  • 實現(xiàn)Cloneable接口鸠天,代表可以克隆
  • 實現(xiàn)了Serializable接口接口,代表可以被序列化

當(dāng)容器被初始化添加元素成功之后帐姻,多個線程讀取容器中的元素粮宛,如果此刻沒有元素的添加,并發(fā)多個線程讀取出來的數(shù)據(jù)大家都是一樣的卖宠,可以理解為線程安全的 巍杈。

如果此刻有個線程往容器中添加一個新的元素,這個時候CopyOnWriteArrayList就會拷貝一個新的數(shù)組出來扛伍,將新的元素添加到新的數(shù)組中筷畦。

在添加元素的這段時間里,如果多線程訪問容器中的元素刺洒,將會讀取到舊的數(shù)據(jù)鳖宾,等添加元素成功之后會將新的引用地址賦值給舊的list引用地址。

代碼分享:

  • add 方法
public boolean add(E e) {
     //加鎖操作
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //獲取原數(shù)組
        Object[] elements = getArray();
        int len = elements.length;
        // 復(fù)制一個新數(shù)組
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 將新的元素添加到新數(shù)組里面
       newElements[len] = e;
        // 將原數(shù)組的引用指向新數(shù)組
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

大家要注意上面的代碼中ReentrantLock逆航,在添加新元素的時候有加鎖操作鼎文,多線程的情況下防止產(chǎn)生臟數(shù)據(jù)。

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

讀的時候不會加鎖因俐,寫的時候會加上鎖拇惋,這個時候如果多線程正好寫數(shù)據(jù)周偎,讀取的時候還是會讀取到舊的數(shù)據(jù)。

  • set方法
public E set(int index, E element) {
   //加鎖
   final ReentrantLock lock = this.lock;
   lock.lock();
   try {
       //獲取原來數(shù)組
       Object[] elements = getArray();
       // 通過索引獲取原來的地址
       E oldValue = get(elements, index);
       // 判斷新舊兩個值是否相等
       if (oldValue != element) {
           int len = elements.length;
           // 拷貝新的數(shù)組
           Object[] newElements = Arrays.copyOf(elements, len);
           //根據(jù)索引修改元素
           newElements[index] = element;
           // 將原數(shù)組的引用指向新數(shù)組
           setArray(newElements);
       } else {
           // Not quite a no-op; ensures volatile write semantics
           //為了確保 voliatile 的語義撑帖,所以盡管寫操作沒有改變數(shù)據(jù)蓉坎,還是調(diào)用set方法
           setArray(elements);
       }
       return oldValue;
   } finally {
       lock.unlock();
   }
}
  • remove方法
public E remove(int index) {  
    final ReentrantLock lock = this.lock;  
    lock.lock();  
    try {  
        Object[] elements = getArray();  
        int len = elements.length;  
        E oldValue = get(elements, index);  
        int numMoved = len - index - 1;  
        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);  
            setArray(newElements);  
        }  
        return oldValue;  
    } finally {  
        lock.unlock();  
    }  
}  

同樣也很簡單,都是使用 System.arraycopy胡嘿、Arrays.copyOf移動元素進(jìn)行元素的刪除操作蛉艾。

  • CopyOnWriteArrayList迭代

針對iterator使用了一個叫COWIterator的迭代器,專門針對CopyOnWrite的迭代器衷敌,因為不支持寫操作勿侯,如上面add、set缴罗、remove都會拋出異常罐监,都是不支持的。

/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code remove}
 *         is not supported by this iterator.
 */
public void remove() {
    throw new UnsupportedOperationException();
}

/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code set}
 *         is not supported by this iterator.
 */
public void set(E e) {
    throw new UnsupportedOperationException();
}

/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code add}
 *         is not supported by this iterator.
 */
public void add(E e) {
    throw new UnsupportedOperationException();
}

舉個例子

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
 
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
    String next = iterator.next();
       // 這句會報錯的??
    iterator.remove();
}
 
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1178)

也正好驗證了迭代的時候UnsupportedOperationException異常瞒爬。

CopyOnWriteArrayList使用場景

從上面的代碼我們可以看出來了弓柱,適用于多讀少寫的場景,比如電商的商品列表侧但,添加新商品和讀取商品就可以用矢空,其他場景小伙伴們可以想想看。

CopyOnWriteArrayList有什么優(yōu)缺點

缺點:

1禀横、內(nèi)存占用屁药,因為寫時復(fù)制的原理,所以在添加新元素的時候會復(fù)制一份柏锄,此刻內(nèi)存中就會有兩份對象酿箭,比如這個時候有200M,你在復(fù)制一份400M趾娃,那么此刻會產(chǎn)生頻繁的JVM的Yong GC和Full GC缭嫡,
嚴(yán)重的會進(jìn)行STW

Java中Stop-The-World機(jī)制簡稱STW,是在執(zhí)行垃圾收集算法時,Java應(yīng)用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。

2抬闷、數(shù)據(jù)一致性問題妇蛀,因為CopyOnWrite容器只能保證最終的數(shù)據(jù)一致性,并不能保證數(shù)據(jù)的實時性笤成,也就是不具備原子性的效果评架。

3、數(shù)據(jù)修改炕泳,隨著數(shù)組的元素越來越多纵诞,修改的時候拷貝數(shù)組將會越來越耗時。

優(yōu)點:

1培遵、多讀少寫浙芙,很多時候我們的系統(tǒng)應(yīng)對的都是讀多寫少的并發(fā)場景登刺,讀操作是無鎖操作所以性能較高。

最后說說

  • Vector
  • ArrayList
  • CopyOnWriteArrayList

這三個集合類都繼承List接口

1茁裙、ArrayList是線程不安全的

2塘砸、Vector是比較古老的線程安全的节仿,但性能不行

3晤锥、CopyOnWriteArrayList在兼顧了線程安全的同時,又提高了并發(fā)性廊宪,性能比Vector要高

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末矾瘾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子箭启,更是在濱河造成了極大的恐慌壕翩,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傅寡,死亡現(xiàn)場離奇詭異放妈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荐操,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門芜抒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人托启,你說我怎么就攤上這事宅倒。” “怎么了屯耸?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵拐迁,是天一觀的道長。 經(jīng)常有香客問我疗绣,道長线召,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任多矮,我火速辦了婚禮灶搜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘工窍。我一直安慰自己割卖,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布患雏。 她就那樣靜靜地躺著鹏溯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淹仑。 梳的紋絲不亂的頭發(fā)上丙挽,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天肺孵,我揣著相機(jī)與錄音,去河邊找鬼颜阐。 笑死平窘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凳怨。 我是一名探鬼主播瑰艘,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肤舞!你這毒婦竟也來了紫新?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤李剖,失蹤者是張志新(化名)和其女友劉穎芒率,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篙顺,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡偶芍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了德玫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匪蟀。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖化焕,靈堂內(nèi)的尸體忽然破棺而出萄窜,到底是詐尸還是另有隱情,我是刑警寧澤撒桨,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布查刻,位于F島的核電站,受9級特大地震影響凤类,放射性物質(zhì)發(fā)生泄漏穗泵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一谜疤、第九天 我趴在偏房一處隱蔽的房頂上張望佃延。 院中可真熱鬧,春花似錦夷磕、人聲如沸履肃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尺棋。三九已至,卻和暖如春绵跷,著一層夾襖步出監(jiān)牢的瞬間膘螟,已是汗流浹背成福。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留荆残,地道東北人奴艾。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像内斯,于是被迫代替她去往敵國和親蕴潦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

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