亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JUC的CopyOnWriteArrayList怎么理解

發布時間:2021-12-21 10:18:16 來源:億速云 閱讀:159 作者:iii 欄目:大數據

這篇文章主要介紹“JUC的CopyOnWriteArrayList怎么理解”,在日常操作中,相信很多人在JUC的CopyOnWriteArrayList怎么理解問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”JUC的CopyOnWriteArrayList怎么理解”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

CopyOnWriteArrayList 是線程安全的 List 實現,底層依賴于數組作為存儲結構,并基于 寫時復制(CoW: Copy-on-Write)機制 保證線程安全性。CopyOnWriteArrayList 在執行修改操作時會將底層數組復制一份,并在副本上實施修改,最后再更新回底層數組。雖然這樣的實現比較消耗內存,但卻帶來了較高的執行效率,屬于拿空間換時間。

除了 CopyOnWriteArrayList,在 JUC 包中還有另外一個基于 CoW 機制實現的線程安全組件,即 CopyOnWriteArraySet,不過 CopyOnWriteArraySet 本質上還是基于 CopyOnWriteArrayList 實現的,所以理解了 CopyOnWriteArrayList 的實現原理,也就同時理解了 CopyOnWriteArraySet 的實現原理。

CopyOnWriteArrayList 實現內幕

CopyOnWriteArrayList 實現自 List 接口,所以我們可以像使用 ArrayList 一樣使用 CopyOnWriteArrayList。CopyOnWriteArrayList 類的字段定義如下:

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {

    /** 支撐同步操作的重入鎖 */
    final transient ReentrantLock lock = new ReentrantLock();
    /** 底層數組 */
    private transient volatile Object[] array;

    // ... 省略方法定義

}

其中 CopyOnWriteArrayList#array 數組是 CopyOnWriteArrayList 的底層存儲結構,CopyOnWriteArrayList 依賴于該數組對數據進行存儲。字段 CopyOnWriteArrayList#lock 對應 ReentrantLock 類型,用于控制多個線程對底層數組的修改操作,保證同一時間只有一個線程對底層數組進行修改。CopyOnWriteArrayList 提供了相應的 CopyOnWriteArrayList#getArrayCopyOnWriteArrayList#setArray 方法用于訪問該字段。

CopyOnWriteArrayList 定義了多個重載的構造方法來初始化構造一個 CopyOnWriteArrayList 對象,實現上比較簡單。下面重點看一下 CopyOnWriteArrayList 之于 List 接口中聲明的核心方法實現,即增(add)、刪(remove)、改(set)、查(get)操作。

首先來看一下 添加元素 的操作,CopyOnWriteArrayList 定義了多個重載版本的 CopyOnWriteArrayList#add 的方法實現,包括:

  • CopyOnWriteArrayList#add(E)

  • CopyOnWriteArrayList#add(int, E)

  • CopyOnWriteArrayList#addIfAbsent(E)

  • CopyOnWriteArrayList#addAll(Collection<? extends E>)

  • CopyOnWriteArrayList#addAll(int, Collection<? extends E>)

  • CopyOnWriteArrayList#addAllAbsent

這些方法在實現上思想都是相通的,下面以 CopyOnWriteArrayList#add(E) 方法為例分析往 CopyOnWriteArrayList 中添加元素的運行機制,方法實現如下:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 加鎖,保證同一時間只有一個線程修改底層數組
    lock.lock();
    try {
        // 獲取底層數組
        Object[] elements = this.getArray();
        int len = elements.length;
        // 復制出一份新的數組,長度加 1,以容納待添加的元素
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        // 更新底層數組
        this.setArray(newElements);
        return true;
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

前面我們已經提及到 CopyOnWriteArrayList 對于數據的修改都是在副本上進行的,上述方法的實現印證了這一點。當往 CopyOnWriteArrayList 中添加一個元素時,方法的執行流程如下:

  1. 獲取鎖對象,保證同一時間只有一個線程在執行修改操作;

  2. 獲取底層數組,基于該數組拷貝出一個新的數組,新數組長度加 1;

  3. 追加待添加元素到新數組的末尾位置,并更新底層數組;

  4. 釋放鎖對象。

再來看一下 獲取元素 的操作,CopyOnWriteArrayList 僅定義了一個 CopyOnWriteArrayList#get 方法,用于獲取指定下標的元素值,實現如下:

public E get(int index) {
    // 直接返回底層數組 index 下標的元素
    return this.get(this.getArray(), index);
}

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

實現上比較簡單,這里主要強調一下弱一致性問題,也就說 get 操作不一定能夠返回最新的值。考慮這樣一個場景,假設有線程 A 和 B,其中 A 調用 get 方法獲取數據,B 調用 add 方法添加數據,當前數組長度為 5:

  1. 線程 B 獲取底層數組 x,然后拷貝出一個長度為 6 的新數組 y,并將待追加的元素設置到 y[5] 的位置,此時還未更新底層數組;

  2. 因為讀操作無需獲取鎖,如果此時線程 A 嘗試獲取下標為 5 的元素,則會拋出 IndexOutOfBoundsException,因為此時 A 獲取到的底層數組還是 x,還沒有更新成 y。

然而,大部分時候這種弱一致性并不會對我們的業務造成影響,但是我們需要知道其存在,以便在發生錯誤時快速定位問題。

繼續來看一下 修改元素 的操作,CopyOnWriteArrayList 同樣僅定義了一個 CopyOnWriteArrayList#set 方法,用于修改指定下標的元素值,實現如下:

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    // 獲取鎖
    lock.lock();
    try {
        // 獲取底層數組
        Object[] elements = this.getArray();
        // 獲取數組 index 下標值
        E oldValue = this.get(elements, index);
        // 待更新的元素值與數組中指定位置的元素值不同
        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            // 更新 index 位置的元素值
            newElements[index] = element;
            // 更新底層數組
            this.setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            // 待更新的元素值與數組中指定位置的元素值相同,無需執行更新操作
            this.setArray(elements); // 保證 volatile 寫語義
        }
        return oldValue;
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

上述方法的執行邏輯如代碼注釋,比較簡單,但是當修改的目標元素值前后未發生變化時還是會調用 CopyOnWriteArrayList#setArray 方法更新一下底層數組,用意何在呢?

代碼中給出的理由是 “Not quite a no-op; ensures volatile write semantics”,也就是說這里的更新操作不完全是一個無意義的操作,可以用來保證 volatile 的寫語義,因為底層數組 array 是 volatile 類型。要理解其用意,可以參考 《深入理解 java 內存模型》一書中的示例(稍作修改):

private int a = 0;
private volatile boolean flag = true;

public void writer() {
    a = 1; // 1
    flag = true; // 2
}

public void reader() {
    if (flag) { // 3
        int i = a; // 4
    }
}

假設線程 A 執行 writer 方法之后,線程 B 執行 reader 方法。根據 happens-before 原則,這個過程建立的 happens-before 關系為:

  1. 根據程序次序規則,1 happens before 2; 3 happens before 4。

  2. 根據 volatile 規則,2 happens before 3。

  3. 根據 happens before 的傳遞性規則,1 happens before 4。

這樣就能保證線程 B 在 reader 方法中讀取 a 變量時能夠看見線程 A 在 writer 方法中對 a 的修改,即使在 writer 方法中對變量 flag 的修改同樣看似多余。

最后來看一下 刪除元素 的操作,CopyOnWriteArrayList 針對刪除操作定義了多個重載版本的 CopyOnWriteArrayList#remove 的方法實現,包括:

  • CopyOnWriteArrayList#remove(int)

  • CopyOnWriteArrayList#remove(java.lang.Object)

  • CopyOnWriteArrayList#removeAll

  • CopyOnWriteArrayList#removeIf

下面以 CopyOnWriteArrayList#remove(int) 方法為例分析從 CopyOnWriteArrayList 中刪除元素的運行機制,方法實現如下:

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    // 獲取鎖
    lock.lock();
    try {
        // 獲取底層數組
        Object[] elements = this.getArray();
        int len = elements.length;
        // 獲取指定下標元素
        E oldValue = this.get(elements, index);
        int numMoved = len - index - 1;
        // 如果是刪除最后一個元素,則直接 copy 即可,無需移動
        if (numMoved == 0) {
            this.setArray(Arrays.copyOf(elements, len - 1));
        } else {
            // 否則,分兩次進行拷貝,去掉原 index 下標的元素
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index, numMoved);
            this.setArray(newElements);
        }
        return oldValue;
    } finally {
        // 釋放鎖
        lock.unlock();
    }
}

刪除的邏輯本質上還是在副本上進行,如上述代碼注釋,其過程與前面分析的操作類似。

除了上面分析的核心操作方法,CopyOnWriteArrayList 還實現了 CopyOnWriteArrayList#iterator 方法返回一個迭代器,實現如下:

public Iterator<E> iterator() {
    return new COWIterator<E>(this.getArray(), 0);
}

關于 COWIterator 的實現不再繼續深入,但是需要知曉的一點是,COWIterator 不支持在迭代過程中修改 CopyOnWriteArrayList 中的元素,對應的 COWIterator#removeCOWIterator#setCOWIterator#add 方法均直接拋出 UnsupportedOperationException 異常。此外,方法 CopyOnWriteArrayList#iterator 返回的是一個弱一致性迭代器,即在迭代期間,其它線程對于底層數組的修改并不會被迭代器看見。

到此,關于“JUC的CopyOnWriteArrayList怎么理解”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

陆川县| 云霄县| 平凉市| 驻马店市| 屯昌县| 珲春市| 西畴县| 平江县| 富民县| 句容市| 穆棱市| 喀喇沁旗| 花莲市| 淄博市| 永城市| 永济市| 乐业县| 漾濞| 安塞县| 敦化市| 仪陇县| 平武县| 兴宁市| 宽城| 麻江县| 奉节县| 双峰县| 咸丰县| 新野县| 香河县| 伊吾县| 同德县| 彭山县| 哈尔滨市| 固原市| 资阳市| 广州市| 临江市| 耿马| 赤壁市| 宣化县|