線程安全的List:CopyOnWriteArrayList

線程安全的List:CopyOnWriteArrayList

并發(fā)包中的并發(fā)List只有CopyOnWriteArrayList让腹。CopyOnWriteArrayList是一個線程安全的ArrayList,對于CopyOnWriteArrayList的操作都是在底層的一個復制的數組上進行的仪或。另外,同在并發(fā)包中的CopyOnWriteArraySet的底層實現也是CopyOnWriteArrayList坏瞄。

add(E e)

? 從(1)處可以發(fā)現CopyOnWriteArrayList使用ReetrantLock作為線程安全的鎖工具墨吓,對于add操作打却,由于獨占鎖的特性填渠,同一時刻只允許一個線程對array進行修改操作弦聂。

? (2)處獲取數組快照,在add方法氛什,由于獨占鎖的存在莺葫,不會有其它線程對array進行修改操作,所以array就是List準確的底層數組枪眉。

? (3)處捺檬,根據獲取到的數組新建一個容量比原數組大一的新數組,并把新增元素放到新數組的最后贸铜。

從以上代碼可以得到CopyOnWriteArrayList的核心思想正如語義堡纬,在寫操作的時候,新建一個數組蒿秦,并把原數組的內容copy到新的數組中烤镐。

public boolean add(E e) {
    // (1)ReentrantLock默認為非公平鎖,
    // 作為獨占鎖同時只允許一個線程對lock()修飾的代碼塊進行操作
    // (在本類中基本是對array的修改操作)
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // (2)獲取array快照
        Object[] elements = getArray();
        int len = elements.length;
        // (3)拷貝array快照到新建數組棍鳖,新建數組的大小為原數組的size+1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        // (4)釋放獨占鎖炮叶,可以競爭
        lock.unlock();
    }
}

// 獲取array快照
final Object[] getArray() {
    return array;
}

addIfAbsent(E e)

這個方法也是CopyOnWriteArraySet的核心方法。

public boolean addIfAbsent(E e) {
    // (0)這里獲取的是快照渡处,因為沒有加鎖镜悉,可能有其它線程有寫操作
    Object[] snapshot = getArray();
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}
 private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            // (1)檢查array是否已經變化,在之前的檢查時医瘫,有其它線程進行了寫操作
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                // (2)取小的那個長度侣肄,進行遍歷對比。
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    // (3) 遍歷對應時醇份,當某個位置元素不相等稼锅,且 current[i]與
                    // 當前需要添加的元素相等, 意味著其它線程新增該元素成功叮喳。
                    // 當前線程失敗。
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                // (4)在current中比快照數組長的部分找到目標元素缰贝,新增失敗
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

正如方法語義:缺少時插入馍悟,方法先查詢待新增元素在當前array中的索引是否大于0,如果大于0證明已經存在該元素剩晴,返回false锣咒,新增失敗。這里要注意的是addIfAbsent()分為兩步赞弥,首先在addIfAbsent(E e)方法中主要做的是檢查該元素是否已存在毅整,如果不存在就調用addIfAbsent(E e, Object[] snapshot)進行真正的新增操作。而傳入的snapshot在(0)處獲取绽左,由于檢查時并沒有進行加鎖操作悼嫉,這意味著snapshot正如其名 ,是快照拼窥,在當前線程A檢查的同時戏蔑,可能有其它線程B已經進行了新增操作將目標元素新增進List。

下圖展示了當在List[1,2,3,4]中新增5時鲁纠,出現代碼標記中(3)(4)情況的可能例子总棵。如果以下兩種情況都未發(fā)生,那么CopyOnWriteArrayList就要做它的老本行了改含,新建數組情龄,拷貝元素。

? 這里需要注意的是拷貝的是current數組而不是snapshot快照數組捍壤。出現(3)情況可能又有另一線程C刪除了元素4骤视。

遍歷對應時,當某個位置元素不相等鹃觉,且 current[i]與當前需要添加的元素相等,
(4)在current中比快照數組長的部分找到目標元素

刪 remove()

remove(int index) 索引刪

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

? 看過add的代碼专酗,remove(int index) 就代碼設計而言沒有太大差別,在這邊主要區(qū)分了兩種情況帜慢,①刪除的元素是中間元素笼裳;②刪除的元素是結尾元素。

①刪除的元素是中間元素
②刪除的元素是結尾元素粱玲。

獲取大小

public int size() {
    return getArray().length;
}

在獲取size時躬柬,僅僅是獲取array快照,并獲取它的長度抽减。并非加鎖操作允青,所以這個size在并發(fā)情況下并不正確,只能在某種程度上反映這個list的大致大小卵沉。

弱一致性的迭代器

public ListIterator<E> listIterator() {
    return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
  /** Snapshot of the array */
  private final Object[] snapshot;
  /** Index of element to be returned by subsequent call to next.  */
  private int cursor;

  private COWIterator(Object[] elements, int initialCursor) {
    cursor = initialCursor;
    snapshot = elements;
  }
  ...
}

可以看到當我們獲取CopyOnWriteArrayList的迭代器的時候颠锉,無鎖獲取array法牲,并把array和0作為構造函數入參傳給迭代器。顯然琼掠,這個迭代器同size()方法一樣拒垃,并不能準確的反應CopyOnWriteArrayList實時的準確情況。

參照

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末瓷蛙,一起剝皮案震驚了整個濱河市悼瓮,隨后出現的幾起案子,更是在濱河造成了極大的恐慌艰猬,老刑警劉巖横堡,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異冠桃,居然都是意外死亡命贴,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門食听,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胸蛛,“玉大人,你說我怎么就攤上這事碳蛋∨呙冢” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵肃弟,是天一觀的道長。 經常有香客問我零蓉,道長笤受,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任敌蜂,我火速辦了婚禮箩兽,結果婚禮上,老公的妹妹穿的比我還像新娘章喉。我一直安慰自己汗贫,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布秸脱。 她就那樣靜靜地躺著落包,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摊唇。 梳的紋絲不亂的頭發(fā)上咐蝇,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音巷查,去河邊找鬼有序。 笑死抹腿,一個胖子當著我的面吹牛,可吹牛的內容都是我干的旭寿。 我是一名探鬼主播警绩,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盅称!你這毒婦竟也來了房蝉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤微渠,失蹤者是張志新(化名)和其女友劉穎搭幻,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體逞盆,經...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡檀蹋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了云芦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俯逾。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖舅逸,靈堂內的尸體忽然破棺而出桌肴,到底是詐尸還是另有隱情,我是刑警寧澤琉历,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布坠七,位于F島的核電站,受9級特大地震影響旗笔,放射性物質發(fā)生泄漏彪置。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一蝇恶、第九天 我趴在偏房一處隱蔽的房頂上張望拳魁。 院中可真熱鬧,春花似錦撮弧、人聲如沸潘懊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽授舟。三九已至,卻和暖如春舌厨,著一層夾襖步出監(jiān)牢的瞬間岂却,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留躏哩,地道東北人署浩。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像扫尺,于是被迫代替她去往敵國和親筋栋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內容