日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

阿里Java面試官:CopyOnWriteArrayList底層是怎么保證線程安全的?

來源: 責編: 時間:2023-11-07 09:14:48 283觀看
導讀歡迎學習解讀Java源碼專欄,在這個系列中,我將手把手帶著大家剖析Java核心組件的源碼,內容包含集合、線程、線程池、并發、隊列等,深入了解其背后的設計思想和實現細節,輕松應對工作面試。引言上篇文章提到ArrayList不是線

歡迎學習解讀Java源碼專欄,在這個系列中,我將手把手帶著大家剖析Java核心組件的源碼,內容包含集合、線程、線程池、并發、隊列等,深入了解其背后的設計思想和實現細節,輕松應對工作面試。m6k28資訊網——每日最新資訊28at.com

引言

上篇文章提到ArrayList不是線程安全的,而CopyOnWriteArrayList是線程安全的。此刻我就會產生幾個問題:m6k28資訊網——每日最新資訊28at.com

  1. CopyOnWriteArrayList初始容量是多少?
  2. CopyOnWriteArrayList是怎么進行擴容的?
  3. CopyOnWriteArrayList是怎么保證線程安全的?

帶著這幾個問題,一起分析一下CopyOnWriteArrayList的源碼。m6k28資訊網——每日最新資訊28at.com

簡介

CopyOnWriteArrayList是一種線程安全的ArrayList,底層是基于數組實現,不過該數組使用了volatile關鍵字修飾。 實現線程安全的原理是,“人如其名”,就是 Copy On Write(寫時復制),意思就是在對其進行修改操作的時候,復制一個新的ArrayList,在新的ArrayList上進行修改操作,從而不影響舊的ArrayList的讀操作。 看一下源碼中CopyOnWriteArrayList內部有哪些數據結構組成:m6k28資訊網——每日最新資訊28at.com

public class CopyOnWriteArrayList<E>    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {    // 加鎖,用來保證線程安全    final transient ReentrantLock lock = new ReentrantLock();    // 存儲元素的數組,使用了volatile修飾    private transient volatile Object[] array;    // 數組的get/set方法    final Object[] getArray() {        return array;    }    final void setArray(Object[] a) {        array = a;    }}

CopyOnWriteArrayList的內部結構非常簡單,使用ReentrantLock加鎖,用來保證線程安全。使用數組存儲元素,數組使用volatile修飾,用來保證內存可見性。當其他線程重新對數組對象進行賦值的時候,當前線程可以及時感知到。m6k28資訊網——每日最新資訊28at.com

初始化

當我們調用CopyOnWriteArrayList的構造方法的時候,底層邏輯是怎么實現的?m6k28資訊網——每日最新資訊28at.com

List<Integer> list = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList初始化的時候,不支持指定數組長度,接著往下看,就能明白CopyOnWriteArrayList為什么不支持指定數組長度。m6k28資訊網——每日最新資訊28at.com

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

初始化過程非常簡單,就是創建了一個長度為0的數組。m6k28資訊網——每日最新資訊28at.com

添加元素

再看一下往CopyOnWriteArrayList添加元素時,調用的 add() 方法源碼實現:m6k28資訊網——每日最新資訊28at.com

// 添加元素public boolean add(E e) {    // 加鎖,保證線程安全    final ReentrantLock lock = this.lock;    lock.lock();    try {        // 獲取原數組        Object[] elements = getArray();        int len = elements.length;        // 創建一個新數組,長度原數組長度+1,并把原數組元素拷貝到新數組里面        Object[] newElements = Arrays.copyOf(elements, len + 1);        // 直接賦值給新數組末尾位置        newElements[len] = e;        // 替換原數組        setArray(newElements);        return true;    } finally {        // 釋放鎖        lock.unlock();    }}

添加元素的流程:m6k28資訊網——每日最新資訊28at.com

  1. 先使用ReentrantLock加鎖,保證線程安全。
  2. 再創建一個新數組,長度是原數組長度+1,并把原數組元素拷貝到新數組里面。
  3. 然后在新數組末尾位置賦值
  4. 使用新數組替換掉原數組
  5. 最后釋放鎖

add() 方法添加元素的時候,并沒有在原數組上進行賦值,而是創建一個新數組,在新數組上賦值后,再用新數組替換原數組。這是為了利用volatile關鍵字的特性,如果直接在原數組上進行修改,其他線程是感知不到的。只有重新對原數組對象進行賦值,其他線程才能感知到。 還有一個需要注意的點是,每次添加元素的時候都會創建一個新數組,并涉及數組拷貝,相當于每次都進行擴容操作。當數組較大,性能消耗較為明顯。所以CopyOnWriteArrayList適用于讀多寫少的場景,如果存在較多的寫操作場景,性能也是一個需要考慮的因素。m6k28資訊網——每日最新資訊28at.com

刪除元素

再看一下刪除元素的方法 remove() 的源碼:m6k28資訊網——每日最新資訊28at.com

// 按照下標刪除元素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) {            // 0表示刪除的是數組末尾的元素            setArray(Arrays.copyOf(elements, len - 1));        } else {            // 創建一個新數組,長度是原數組長度-1            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();    }}

刪除元素的流程:m6k28資訊網——每日最新資訊28at.com

  1. 先使用ReentrantLock加鎖,保證線程安全。
  2. 再創建一個新數組,長度是原數組長度-1,并把原數組中剩余元素(不包含需要刪除的元素)拷貝到新數組里面。
  3. 使用新數組替換掉原數組
  4. 最后釋放鎖

可以看到,刪除元素的流程與添加元素的流程類似,都是需要創建一個新數組,再把舊數組元素拷貝到新數組,最后替換舊數組。區別就是新數組的長度不一樣,刪除元素流程中的新數組長度是舊數組長度-1,添加元素流程中的新數組長度是舊數組長度+1。 根據對象刪除元素的方法源碼與之類似,也是轉換成下標刪除,讀者可自行查看。m6k28資訊網——每日最新資訊28at.com

批量刪除

再看一下批量刪除元素方法 removeAll() 的源碼:m6k28資訊網——每日最新資訊28at.com

// 批量刪除元素public boolean removeAll(Collection<?> c) {    // 參數判空    if (c == null) {        throw new NullPointerException();    }    // 加鎖,保證線程安全    final ReentrantLock lock = this.lock;    lock.lock();    try {        // 獲取原數組        Object[] elements = getArray();        int len = elements.length;        if (len != 0) {            // 創建一個新數組,長度暫時使用原數組的長度,因為不知道要刪除多少個元素。            Object[] temp = new Object[len];            // newlen表示新數組中元素個數            int newlen = 0;            // 遍歷原數組,把需要保留的元素放到新數組中            for (int i = 0; i < len; ++i) {                Object element = elements[i];                if (!c.contains(element)) {                    temp[newlen++] = element;                }            }            // 如果新數組沒有滿,就釋放空白位置,并覆蓋原數組            if (newlen != len) {                setArray(Arrays.copyOf(temp, newlen));                return true;            }        }        return false;    } finally {        // 釋放鎖        lock.unlock();    }}

批量刪除元素的流程,與上面類似:m6k28資訊網——每日最新資訊28at.com

  1. 先使用ReentrantLock加鎖,保證線程安全。
  2. 再創建一個新數組,長度暫時使用原數組的長度,因為不知道要刪除多少個元素。
  3. 然后遍歷原數組,把需要保留的元素放到新數組中。
  4. 釋放掉新數組中空白位置,再使用新數組替換掉原數組。
  5. 最后釋放鎖

如果遇到需要一次刪除多個元素的場景,盡量使用 removeAll() 方法,因為 removeAll() 方法只涉及一次數組拷貝,性能比單個刪除元素更好。m6k28資訊網——每日最新資訊28at.com

并發修改問題

當遍歷CopyOnWriteArrayList的過程中,同時增刪CopyOnWriteArrayList中的元素,會發生什么情況?測試一下:m6k28資訊網——每日最新資訊28at.com

import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class Test {    public static void main(String[] args) {        List<Integer> list = new CopyOnWriteArrayList<>();        list.add(1);        list.add(2);        list.add(2);        list.add(3);        // 遍歷ArrayList        for (Integer key : list) {            // 判斷如果元素等于2,則刪除            if (key.equals(2)) {                list.remove(key);            }        }        System.out.println(list);    }}

輸出結果:m6k28資訊網——每日最新資訊28at.com

[1, 3]

不但沒有拋出異常,還把CopyOnWriteArrayList中重復的元素也都刪除了。 原因是CopyOnWriteArrayList重新實現迭代器,拷貝了一份原數組的快照,在快照數組上進行遍歷。這樣做的優點是其他線程對數組的并發修改,不影響對快照數組的遍歷,但是遍歷過程中無法感知其他線程對數組修改,有得必有失。 下面是迭代器的源碼實現:m6k28資訊網——每日最新資訊28at.com

static final class COWIterator<E> implements ListIterator<E> {    /**     * 原數組的快照     */    private final Object[] snapshot;    /**     * 迭代游標     */    private int cursor;    private COWIterator(Object[] elements, int initialCursor) {        cursor = initialCursor;        snapshot = elements;    }    public boolean hasNext() {        return cursor < snapshot.length;    }    // 迭代下個元素    public E next() {        if (!hasNext())            throw new NoSuchElementException();        return (E)snapshot[cursor++];    }}

總結

現在可以回答文章開頭提出的問題了吧:m6k28資訊網——每日最新資訊28at.com

  1. CopyOnWriteArrayList初始容量是多少?

答案:是0m6k28資訊網——每日最新資訊28at.com

  1. CopyOnWriteArrayList是怎么進行擴容的?

答案:m6k28資訊網——每日最新資訊28at.com

  • 加鎖
  • 創建一個新數組,長度原數組長度+1,并把原數組元素拷貝到新數組里面。
  • 釋放鎖
  1. CopyOnWriteArrayList是怎么保證線程安全的?

答案:m6k28資訊網——每日最新資訊28at.com

  • 使用ReentrantLock加鎖,保證操作過程中線程安全。
  • 使用volatile關鍵字修飾數組,保證當前線程對數組對象重新賦值后,其他線程可以及時感知到。

本文鏈接:http://www.www897cc.com/showinfo-26-17411-0.html阿里Java面試官:CopyOnWriteArrayList底層是怎么保證線程安全的?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 虛擬線程原理及性能分析

下一篇: Instagram 早期技術架構,你了解了嗎?

標簽:
  • 熱門焦點
  • 一加Ace2 Pro真機揭曉 鈦空灰配色質感拉滿

    終于,在經過了幾波預熱之后,一加Ace2 Pro的外觀真機圖在網上出現了。還是博主數碼閑聊站曝光的,這次的外觀設計還是延續了一加11的方案,只是細節上有了調整,例如新加入了鈦空灰
  • 影音體驗是真的強 簡單聊聊iQOO Pad

    大公司的好處就是產品線豐富,非常細分化的東西也能給你做出來,例如早先我們看到了新的vivo Pad2,之后我們又在iQOO Neo8 Pro的發布會上看到了iQOO的首款平板產品iQOO Pad。雖
  • 2023年Q2用戶偏好榜:12+256G版本成新主流

    3月份的性能榜、性價比榜和好評榜之后,就要輪到2023年的第二季度偏好榜了,上半年的新機潮已經過去,最明顯的肯定就是大內存和存儲的機型了,另外部分中端機也取消了屏幕塑料支架
  • 把LangChain跑起來的三個方法

    使用LangChain開發LLM應用時,需要機器進行GLM部署,好多同學第一步就被勸退了,那么如何繞過這個步驟先學習LLM模型的應用,對Langchain進行快速上手?本片講解3個把LangChain跑起來
  • 三萬字盤點 Spring 九大核心基礎功能

    大家好,我是三友~~今天來跟大家聊一聊Spring的9大核心基礎功能。話不多說,先上目錄:圖片友情提示,本文過長,建議收藏,嘿嘿嘿!一、資源管理資源管理是Spring的一個核心的基礎功能,不
  • 猿輔導與新東方的兩種“歸途”

    作者|卓心月 出品|零態LT(ID:LingTai_LT)如何成為一家偉大企業?答案一定是對&ldquo;勢&rdquo;的把握,這其中最關鍵的當屬對企業戰略的制定,且能夠站在未來看現在,即使這其中的
  • 滴滴違法違規被罰80.26億 共存在16項違法事實

    滴滴違法違規被罰80.26億 存在16項違法事實開始于2121年7月,歷經一年時間,網絡安全審查辦公室對“滴滴出行”網絡安全審查終于有了一個暫時的結束。據“網信
  • 上海舉辦人工智能大會活動,建設人工智能新高地

    人工智能大會在上海浦江兩岸隆重拉開帷幕,人工智能新技術、新產品、新應用、新理念集中亮相。8月30日晚,作為大會的特色活動之一的上海人工智能發展盛典人工
  • 2021中國國際消費電子博覽會與青島國際軟件融合創新博覽會新聞發布會隆重舉行

    9月18日,2021中國國際消費電子博覽會與青島國際軟件融合創新博覽會新聞發布會在青島國際新聞中心隆重舉行。發布會上青島市政府領導聯袂出席,對本次雙展會情
Top 主站蜘蛛池模板: 深圳市| 嘉义县| 昌都县| 吕梁市| 巴马| 平原县| 华坪县| 镇宁| 裕民县| 勐海县| 湘阴县| 浦北县| 明光市| 克什克腾旗| 明水县| 象山县| 临海市| 逊克县| 锡林浩特市| 称多县| 修水县| 枝江市| 仁化县| 德安县| 苏尼特右旗| 兰坪| 贵港市| 浪卡子县| 潍坊市| 洪江市| 安新县| 海口市| 昌都县| 依兰县| 怀仁县| 棋牌| 东明县| 大厂| 定南县| 响水县| 孙吴县|