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

當(dāng)前位置:首頁 > 科技  > 軟件

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

來源: 責(zé)編: 時(shí)間:2023-11-07 09:14:48 314觀看
導(dǎo)讀歡迎學(xué)習(xí)解讀Java源碼專欄,在這個(gè)系列中,我將手把手帶著大家剖析Java核心組件的源碼,內(nèi)容包含集合、線程、線程池、并發(fā)、隊(duì)列等,深入了解其背后的設(shè)計(jì)思想和實(shí)現(xiàn)細(xì)節(jié),輕松應(yīng)對(duì)工作面試。引言上篇文章提到ArrayList不是線

歡迎學(xué)習(xí)解讀Java源碼專欄,在這個(gè)系列中,我將手把手帶著大家剖析Java核心組件的源碼,內(nèi)容包含集合、線程、線程池、并發(fā)、隊(duì)列等,深入了解其背后的設(shè)計(jì)思想和實(shí)現(xiàn)細(xì)節(jié),輕松應(yīng)對(duì)工作面試。zRB28資訊網(wǎng)——每日最新資訊28at.com

引言

上篇文章提到ArrayList不是線程安全的,而CopyOnWriteArrayList是線程安全的。此刻我就會(huì)產(chǎn)生幾個(gè)問題:zRB28資訊網(wǎng)——每日最新資訊28at.com

  1. CopyOnWriteArrayList初始容量是多少?
  2. CopyOnWriteArrayList是怎么進(jìn)行擴(kuò)容的?
  3. CopyOnWriteArrayList是怎么保證線程安全的?

帶著這幾個(gè)問題,一起分析一下CopyOnWriteArrayList的源碼。zRB28資訊網(wǎng)——每日最新資訊28at.com

簡介

CopyOnWriteArrayList是一種線程安全的ArrayList,底層是基于數(shù)組實(shí)現(xiàn),不過該數(shù)組使用了volatile關(guān)鍵字修飾。 實(shí)現(xiàn)線程安全的原理是,“人如其名”,就是 Copy On Write(寫時(shí)復(fù)制),意思就是在對(duì)其進(jìn)行修改操作的時(shí)候,復(fù)制一個(gè)新的ArrayList,在新的ArrayList上進(jìn)行修改操作,從而不影響舊的ArrayList的讀操作。 看一下源碼中CopyOnWriteArrayList內(nèi)部有哪些數(shù)據(jù)結(jié)構(gòu)組成:zRB28資訊網(wǎng)——每日最新資訊28at.com

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

CopyOnWriteArrayList的內(nèi)部結(jié)構(gòu)非常簡單,使用ReentrantLock加鎖,用來保證線程安全。使用數(shù)組存儲(chǔ)元素,數(shù)組使用volatile修飾,用來保證內(nèi)存可見性。當(dāng)其他線程重新對(duì)數(shù)組對(duì)象進(jìn)行賦值的時(shí)候,當(dāng)前線程可以及時(shí)感知到。zRB28資訊網(wǎng)——每日最新資訊28at.com

初始化

當(dāng)我們調(diào)用CopyOnWriteArrayList的構(gòu)造方法的時(shí)候,底層邏輯是怎么實(shí)現(xiàn)的?zRB28資訊網(wǎng)——每日最新資訊28at.com

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

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

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

初始化過程非常簡單,就是創(chuàng)建了一個(gè)長度為0的數(shù)組。zRB28資訊網(wǎng)——每日最新資訊28at.com

添加元素

再看一下往CopyOnWriteArrayList添加元素時(shí),調(diào)用的 add() 方法源碼實(shí)現(xiàn):zRB28資訊網(wǎng)——每日最新資訊28at.com

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

添加元素的流程:zRB28資訊網(wǎng)——每日最新資訊28at.com

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

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

刪除元素

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

// 按照下標(biāo)刪除元素public E remove(int index) {    // 加鎖,保證線程安全    final ReentrantLock lock = this.lock;    lock.lock();    try {        // 獲取原數(shù)組        Object[] elements = getArray();        int len = elements.length;        E oldValue = get(elements, index);        // 計(jì)算需要移動(dòng)的元素個(gè)數(shù)        int numMoved = len - index - 1;        if (numMoved == 0) {            // 0表示刪除的是數(shù)組末尾的元素            setArray(Arrays.copyOf(elements, len - 1));        } else {            // 創(chuàng)建一個(gè)新數(shù)組,長度是原數(shù)組長度-1            Object[] newElements = new Object[len - 1];            // 把原數(shù)組下標(biāo)前后兩段的元素都拷貝到新數(shù)組里面            System.arraycopy(elements, 0, newElements, 0, index);            System.arraycopy(elements, index + 1, newElements, index,                numMoved);            // 替換原數(shù)組            setArray(newElements);        }        return oldValue;    } finally {        // 釋放鎖        lock.unlock();    }}

刪除元素的流程:zRB28資訊網(wǎng)——每日最新資訊28at.com

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

可以看到,刪除元素的流程與添加元素的流程類似,都是需要?jiǎng)?chuàng)建一個(gè)新數(shù)組,再把舊數(shù)組元素拷貝到新數(shù)組,最后替換舊數(shù)組。區(qū)別就是新數(shù)組的長度不一樣,刪除元素流程中的新數(shù)組長度是舊數(shù)組長度-1,添加元素流程中的新數(shù)組長度是舊數(shù)組長度+1。 根據(jù)對(duì)象刪除元素的方法源碼與之類似,也是轉(zhuǎn)換成下標(biāo)刪除,讀者可自行查看。zRB28資訊網(wǎng)——每日最新資訊28at.com

批量刪除

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

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

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

  1. 先使用ReentrantLock加鎖,保證線程安全。
  2. 再創(chuàng)建一個(gè)新數(shù)組,長度暫時(shí)使用原數(shù)組的長度,因?yàn)椴恢酪獎(jiǎng)h除多少個(gè)元素。
  3. 然后遍歷原數(shù)組,把需要保留的元素放到新數(shù)組中。
  4. 釋放掉新數(shù)組中空白位置,再使用新數(shù)組替換掉原數(shù)組。
  5. 最后釋放鎖

如果遇到需要一次刪除多個(gè)元素的場景,盡量使用 removeAll() 方法,因?yàn)?nbsp;removeAll() 方法只涉及一次數(shù)組拷貝,性能比單個(gè)刪除元素更好。zRB28資訊網(wǎng)——每日最新資訊28at.com

并發(fā)修改問題

當(dāng)遍歷CopyOnWriteArrayList的過程中,同時(shí)增刪CopyOnWriteArrayList中的元素,會(huì)發(fā)生什么情況?測試一下:zRB28資訊網(wǎng)——每日最新資訊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);    }}

輸出結(jié)果:zRB28資訊網(wǎng)——每日最新資訊28at.com

[1, 3]

不但沒有拋出異常,還把CopyOnWriteArrayList中重復(fù)的元素也都刪除了。 原因是CopyOnWriteArrayList重新實(shí)現(xiàn)迭代器,拷貝了一份原數(shù)組的快照,在快照數(shù)組上進(jìn)行遍歷。這樣做的優(yōu)點(diǎn)是其他線程對(duì)數(shù)組的并發(fā)修改,不影響對(duì)快照數(shù)組的遍歷,但是遍歷過程中無法感知其他線程對(duì)數(shù)組修改,有得必有失。 下面是迭代器的源碼實(shí)現(xiàn):zRB28資訊網(wǎng)——每日最新資訊28at.com

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

總結(jié)

現(xiàn)在可以回答文章開頭提出的問題了吧:zRB28資訊網(wǎng)——每日最新資訊28at.com

  1. CopyOnWriteArrayList初始容量是多少?

答案:是0zRB28資訊網(wǎng)——每日最新資訊28at.com

  1. CopyOnWriteArrayList是怎么進(jìn)行擴(kuò)容的?

答案:zRB28資訊網(wǎng)——每日最新資訊28at.com

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

答案:zRB28資訊網(wǎng)——每日最新資訊28at.com

  • 使用ReentrantLock加鎖,保證操作過程中線程安全。
  • 使用volatile關(guān)鍵字修飾數(shù)組,保證當(dāng)前線程對(duì)數(shù)組對(duì)象重新賦值后,其他線程可以及時(shí)感知到。

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

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

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

下一篇: Instagram 早期技術(shù)架構(gòu),你了解了嗎?

標(biāo)簽:
  • 熱門焦點(diǎn)
  • 一加Ace2 Pro真機(jī)揭曉 鈦空灰配色質(zhì)感拉滿

    終于,在經(jīng)過了幾波預(yù)熱之后,一加Ace2 Pro的外觀真機(jī)圖在網(wǎng)上出現(xiàn)了。還是博主數(shù)碼閑聊站曝光的,這次的外觀設(shè)計(jì)還是延續(xù)了一加11的方案,只是細(xì)節(jié)上有了調(diào)整,例如新加入了鈦空灰
  • 跑分安卓第一!Redmi K60至尊版8月發(fā)布!盧偉冰:目標(biāo)年度性能之王

    8月5日消息,Redmi K60至尊版將于8月發(fā)布,在此前舉行的戰(zhàn)略發(fā)布會(huì)上,官方該機(jī)將搭載搭載天璣9200+處理器,安兔兔V10跑分超177萬分,是目前安卓陣營最高的分?jǐn)?shù)
  • 一加首款折疊屏!一加Open渲染圖出爐:罕見單手可握小尺寸

    8月5日消息,此前就有爆料稱,一加首款折疊屏手機(jī)將會(huì)在第三季度上市,如今隨著時(shí)間臨近,新機(jī)的各種消息也開始浮出水面。據(jù)悉,這款新機(jī)將會(huì)被命名為&ldquo;On
  • Golang 中的 io 包詳解:組合接口

    io.ReadWriter// ReadWriter is the interface that groups the basic Read and Write methods.type ReadWriter interface { Reader Writer}是對(duì)Reader和Writer接口的組合,
  • SpringBoot中使用Cache提升接口性能詳解

    環(huán)境:springboot2.3.12.RELEASE + JSR107 + Ehcache + JPASpring 框架從 3.1 開始,對(duì) Spring 應(yīng)用程序提供了透明式添加緩存的支持。和事務(wù)支持一樣,抽象緩存允許一致地使用各
  • 多線程開發(fā)帶來的問題與解決方法

    使用多線程主要會(huì)帶來以下幾個(gè)問題:(一)線程安全問題  線程安全問題指的是在某一線程從開始訪問到結(jié)束訪問某一數(shù)據(jù)期間,該數(shù)據(jù)被其他的線程所修改,那么對(duì)于當(dāng)前線程而言,該線程
  • 慕巖炮轟抖音,百合網(wǎng)今何在?

    來源:價(jià)值研究所 作者:Hernanderz&ldquo;難道就因?yàn)樽约旱囊粋€(gè)產(chǎn)品牛逼了,從客服到總裁,都不愿意正視自己產(chǎn)品和運(yùn)營上的問題,選擇逃避了嗎?&rdquo;這一番話,出自百合網(wǎng)聯(lián)合創(chuàng)
  • 一條抖音4億人圍觀 ! 這家MCN比無憂傳媒還野

    作者:Hiu 來源:互聯(lián)網(wǎng)品牌官01 擦邊少女空降熱搜,幕后推手曝光被網(wǎng)友譽(yù)為&ldquo;純欲天花板&rdquo;的女網(wǎng)紅井川里予,近期因?yàn)橐唤M哥特風(fēng)照片登上熱搜,引發(fā)了一場互聯(lián)網(wǎng)世界關(guān)于
  • 中關(guān)村論壇11月25日開幕,15位諾獎(jiǎng)級(jí)大咖將發(fā)表演講

    11月18日,記者從2022中關(guān)村論壇新聞發(fā)布會(huì)上獲悉,中關(guān)村論壇將于11月25至30日在京舉行。本屆中關(guān)村論壇由科學(xué)技術(shù)部、國家發(fā)展改革委、工業(yè)和信息化部、國務(wù)
Top 主站蜘蛛池模板: 济南市| 赤水市| 西青区| 盖州市| 墨江| 临澧县| 香港| 郸城县| 伽师县| 股票| 肃南| 安西县| 梁平县| 竹溪县| 金华市| 石楼县| 陇南市| 武义县| 江孜县| 海阳市| 赤壁市| 松溪县| 深州市| 和田市| 祁门县| 旬邑县| 双峰县| 治多县| 赣榆县| 渭南市| 左权县| 桑植县| 连平县| 资源县| 马山县| 上饶市| 安庆市| 新巴尔虎左旗| 治县。| 汶上县| 响水县|