juc系列-CopyOnWriteArrayList

CopyOnWriteArrayList是一個ArrayList線程安全的變體。當數組內容有所變化時,拷貝一份新的出來,在新對象上進行修改操作,完成后把新對象引用賦值給array屬性。每發生一次改變,就需要復制一份數據,這樣復制是需要一定開銷的,所以CopyOnWriteArrayList適合讀操作遠大于修改操作的情況中。

CopyOnWriteArrayList構造函數:

public CopyOnWriteArrayList()

//Collection做初始化參數
public CopyOnWriteArrayList(Collection<? extends E> c)

//Array做初始化參數
public CopyOnWriteArrayList(E[] toCopyIn)

1.讀操作

privateE get(Object[] a, intindex) {
    return(E) a[index];
}
 
publicE get(intindex) {
    returnget(getArray(), index);
}

直接讀取,不需要加鎖,因為即使讀取過程中有其他線程改動List,也是開辟新的數組并在新數組上改動,舊數組對象始終是可用的。

2.寫操作
在CopyOnWriteArrayList中寫操作過程大致是這樣的。在原有數組的基礎上拷貝一份新的數組(容器副本),將改動操作在新數組上完成(即把新增元素加入新數組中),然后再把新數組對象的引用賦給CopyOnWriteArrayList的array。顯然,在多線程環境中,為了保證線程安全,整個過程需要加鎖。所以CopyOnWriteArrayList的寫操作性能損耗是很大的,一方面需要競爭獲取鎖,另一方面需要進行復制操作。

下面以add(int index, E element)方法為例說明CopyOnWriteArrayList的修改操作:

//指定位置增加元素
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        //修改array前獲取鎖
        lock.lock();
        try {
        //獲取原有array
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
            //index=length,即數組尾部新增一個元素,同add(E element)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
            //兩次復制
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //新增元素
            newElements[index] = element;
            //將新數組引用賦值給array
            setArray(newElements);
        } finally {
        //釋放鎖
            lock.unlock();
        }
    }

除了add方法,還有remove、removeRange、addIfAbsent等其他修改操作原理都是一樣的,都是新new一個數組對象,在新array上進行修改操作,完事后再將新數組引用賦值給實例變量array,當然修改操作都是需要加鎖的。

通過Iterator迭代器遍歷CopyOnWriteArrayList

通過Iterator遍歷CopyOnWriteArrayList的時候,不允許對array進行修改。remove、add、set方法直接拋出UnsupportedOperationException異常,這點是和普通list不同的地方。

線程安全性

我們可以看到,CopyOnWriteArrayList內部的array數組對象從被創建,到這個對象生命結束,是不可變的。變的是array變量的引用值,每做一次修改操作,array變量就指向新生成的數組對象,原對象被gc,如此反復。這種方式核心思想是減少鎖競爭,從而提高高并發時的讀取性能,但一定程度上犧牲了寫的性能。
由此可得:“寫入時復制(Copy-On-Write)”容器的線程安全性在于:只有正確地發布一個事實不可變的對象,那么在訪問該對象時就不需要做同步操作。這也就解釋了為什么通過迭代器Iterator是不允許進行修改操作的了。

timestamp_1470147855946_test.png
優點

讀操作無需加鎖,并發環境性能不錯,但只適用于讀操作遠大于寫操作的場景。

缺陷
  1. 缺少同步控制,數據的一致性沒法保證。在并發環境中,一個線程在修改array的時候,其他線程是可以進行讀操作的,只是讀取的array任然是舊數據。所以對數據實時一致性要求高的場景,只能另尋它法。
  2. 每次修改都需要進行復制操作,如果list中存放的大對象,則復制時會產生兩個對象,會對內存產生一定壓力。所以大對象謹慎使用CopyOnWriteArrayList。
CopyOnWriteArraySet

CopyOnWriteArraySet完全基于CopyOnWriteArrayList實現。部分源碼如下:

public class CopyOnWriteArraySet<E> extends AbstractSet<E>{
//內部維護了一個CopyOnWriteArrayList對象
    private final CopyOnWriteArrayList<E> al;

    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

    public CopyOnWriteArraySet(Collection<? extends E> c) {
        al = new CopyOnWriteArrayList<E>();
        al.addAllAbsent(c);
    }
    //其所有操作都是代理給內部的CopyOnWriteArrayList對象執行。
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
    public boolean remove(Object o) {
        return al.remove(o);
    }
    public Iterator<E> iterator() {
        return al.iterator();
    }    
    public Object[] toArray() {
        return al.toArray();
    }
    。。。
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容

  • java.util.concurrent.CopyOnWriteArrayList 用于替代同步List,在某些情...
    FX_SKY閱讀 387評論 0 0
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,722評論 0 11
  • java筆記第一天 == 和 equals ==比較的比較的是兩個變量的值是否相等,對于引用型變量表示的是兩個變量...
    jmychou閱讀 1,515評論 0 3
  • 對于大學逃課,可能是一種常態,至少在我們學校是這樣的,而且我也有問過我的一些同學,似乎大致相同。并且本人也是一...
    流水的中庸閱讀 1,159評論 2 2
  • 接完一個網上的個案咨詢,已是深夜,卻沒有了睡意,我知道是內在的愉悅在做崇,因為我發覺我慢慢的開始佩服自己,又...
    寧靜牛媽閱讀 646評論 0 0