最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Java中ArrayList線程不安全怎么辦

 更新時(shí)間:2021年08月30日 11:33:04   作者:諾狗w  
本文主要介紹了Java中ArrayList線程不安全怎么辦,主要有三種解決的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

ArrayList線程不安全怎么辦?

有三種解決方法:

使用對(duì)應(yīng)的 Vector 類(lèi),這個(gè)類(lèi)中的所有方法都加上了 synchronized 關(guān)鍵字

  • 就和 HashMap 和 HashTable 的關(guān)系一樣

使用 Collections 提供的 synchronizedList 方法,將一個(gè)原本線程不安全的集合類(lèi)轉(zhuǎn)換為線程安全的,使用方法如下:

List<Integer> list = Collections.synchronizedList(new ArrayList<>());

其實(shí) HashMap 也可以用這招:

Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

這個(gè)看上去有點(diǎn)東西,其實(shí)也是給每個(gè)方法加上一個(gè) synchronized,不過(guò)不是直接加在方法上,而是加在方法內(nèi)部,只有當(dāng)線程獲取到 mutex 這個(gè)對(duì)象的鎖,才能進(jìn)入代碼塊:

public E get(int index) {
    synchronized (mutex) {
        return list.get(index);
    }
}

使用 JUC 包下提供的 CopyOnWriteArrayList 類(lèi)

  • 其實(shí) ConcurrentHashMap 也是 JUC 包下的

這里具體討論一下 CopyOnWriteArrayList 這個(gè)類(lèi),它采用了“寫(xiě)時(shí)復(fù)制”的技術(shù),也就是說(shuō),每當(dāng)要往這個(gè) list 中添加元素時(shí),并不是直接就添加了,而是會(huì)先復(fù)制一份 list,然后在這個(gè)復(fù)制中添加元素,最后再修改指針的指向,看看 add 的源碼:

public boolean add(E e) {
    synchronized (lock) {
        //得到當(dāng)前的數(shù)組
        Object[] es = getArray();
        int len = es.length;
        //復(fù)制一份并擴(kuò)容
        es = Arrays.copyOf(es, len + 1);
        //把新元素添加進(jìn)去
        es[len] = e;
        //修改指針的指向
        setArray(es);
        return true;
    }
}

有人可能會(huì)疑惑,這有什么意義,這不也加了 synchronized 嗎,而且還要復(fù)制數(shù)組,這**不是比 Vector 還要爛嗎?

確實(shí)是這樣的,在寫(xiě)操作比較多的場(chǎng)景下,CopyOnWriteArrayList 確實(shí)比 Vector 還要慢,但它有兩個(gè)優(yōu)勢(shì):

雖然寫(xiě)操作爛了,但讀操作快了很多,因?yàn)樵?vector 中,讀操作也是需要鎖的,而在這里,讀操作就不需要鎖了,get 方法比較短可能不便于理解,我們看看 indexOf 這個(gè)方法:

public int indexOf(Object o) {
    Object[] es = getArray();
    return indexOfRange(o, es, 0, es.length);
}
private static int indexOfRange(Object o, Object[] es, int from, int to) {
    if (o == null) {
        for (int i = from; i < to; i++)
            if (es[i] == null)
                return i;
    } else {
        //****here****
        for (int i = from; i < to; i++)
            if (o.equals(es[i]))
                return i;
    }
    return -1;
}

可以發(fā)現(xiàn),這個(gè)方法先把當(dāng)前數(shù)組 array 交給了 es 這個(gè)變量,后續(xù)的所有操作都是基于 es 進(jìn)行的(此時(shí) array 和 es 都指向內(nèi)存中的同一份數(shù)組 a1)

由于所有寫(xiě)操作都是在 a1 的拷貝上進(jìn)行的(我們把內(nèi)存中的這份拷貝稱(chēng)為 a2),因此不會(huì)影響到那些正在 a1 上進(jìn)行的讀操作,并且就算寫(xiě)操作執(zhí)行完畢了,array 指向了 a2,也不會(huì)影響到 es 這個(gè)數(shù)組,因?yàn)?es 指向的還是 a1

試想,如果 vector 的讀操作不加鎖會(huì)出現(xiàn)什么情況?由于 vector 中所有的讀寫(xiě)操作都是基于同一個(gè)數(shù)組的,因此雖然讀操作一開(kāi)始拿到的數(shù)組是沒(méi)問(wèn)題的,但在后續(xù)遍歷的過(guò)程中(比如上面代碼標(biāo)注了 here 的地方),很可能出現(xiàn)其他線程對(duì)數(shù)組進(jìn)行了修改,夸張點(diǎn)說(shuō),如果有個(gè)線程把數(shù)組給清空了,那么讀操作就肯定會(huì)報(bào)錯(cuò)了,而對(duì)于 CopyOnWriteArrayList 來(lái)說(shuō),就算有清空的操作,那也是在 a2 上進(jìn)行的,而讀操作還是在 a1 上進(jìn)行,不會(huì)有任何影響

在 forEach 遍歷一個(gè) vector 時(shí),是不允許對(duì) vector 進(jìn)行修改的,會(huì)報(bào)出 ConcurrentModificationException 這個(gè)異常,理由很簡(jiǎn)單,因?yàn)橹挥幸环輸?shù)組,要是遍歷到一半有其它線程把數(shù)組清空了不就出問(wèn)題了嗎,因此 java 干脆就直接禁止這種遍歷時(shí)修改數(shù)組的行為了,但對(duì)于 CopyOnWriteArrayList 來(lái)說(shuō),它的遍歷是一直在 a1 上進(jìn)行的,其它寫(xiě)線程只能修改到 a2,這對(duì) a1 是沒(méi)有任何影響的,我們看一段代碼來(lái)驗(yàn)證一下:

public class Test {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        //遍歷時(shí)把數(shù)組清空
        for (Integer i : list) {
            System.out.println(i);
            list.clear();
        }
    }
}

結(jié)果是沒(méi)有報(bào)錯(cuò),并且完整輸出了 0~999 所有的數(shù)字,可見(jiàn)這里遍歷的就是最開(kāi)始的那個(gè)數(shù)組 a1,期間哪怕有再多的寫(xiě)操作也不會(huì)影響到 a1,因?yàn)樗械膶?xiě)操作都是在 a2 a3 a4 上進(jìn)行的
綜上所述,CopyOnWriteArrayList 的優(yōu)點(diǎn)有兩個(gè):

  • 讀操作不需要鎖,因此讀讀可以并發(fā),讀寫(xiě)也能并發(fā),性能較好
  • forEach 遍歷時(shí)也不需要鎖(其實(shí)遍歷也算是一種讀操作吧),主要是遍歷時(shí)數(shù)組可以被修改,不會(huì)報(bào)錯(cuò)(因?yàn)楸闅v的是 a1,改的是 a2 a3,對(duì) a1 不會(huì)有影響)

但它的缺點(diǎn)也很明顯,主要有兩點(diǎn):

  • 首先,寫(xiě)操作的內(nèi)存消耗非常大,每次修改數(shù)組都會(huì)進(jìn)行一次拷貝,如果數(shù)組比較大或者修改次數(shù)比較多,很快就會(huì)消耗掉大量?jī)?nèi)存,觸發(fā) GC,因此在寫(xiě)多的場(chǎng)景下一定要慎用這個(gè)類(lèi)
  • 其次,所有讀操作和 forEach 遍歷都是基于舊數(shù)組 a1 的,就算遍歷途中新增了一個(gè)很重要的數(shù)據(jù),這個(gè)數(shù)據(jù)也是在 a2 中,遍歷 a1 是無(wú)法得到這個(gè)數(shù)據(jù)的,總之就是,所有的讀操作一旦開(kāi)始,就無(wú)法再感知到最新的那些數(shù)據(jù)

可以發(fā)現(xiàn)一個(gè)有趣的事情,就是成也舊數(shù)組,敗也舊數(shù)組,正因?yàn)樗凶x取都是基于舊數(shù)組 a1 的,因此可以不加鎖就大膽進(jìn)行,不怕有線程把數(shù)組改了,因?yàn)楦膭?dòng)都是在 a2 a3 上的,跟 a1 沒(méi)有關(guān)系,但也正因?yàn)樗凶x取都是基于舊數(shù)組 a1 的,因此一旦讀取操作開(kāi)始,就算有線程在數(shù)組中加入了一個(gè)很重要的數(shù)據(jù),這個(gè)讀取操作也是感知不到這個(gè)最新的數(shù)據(jù)的,因?yàn)檫@個(gè)最新的數(shù)據(jù)只會(huì)在 a2 中有

到此這篇關(guān)于淺談Java中ArrayList線程不安全怎么辦的文章就介紹到這了,更多相關(guān)ArrayList線程不安全內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot監(jiān)控Tomcat活動(dòng)線程數(shù)來(lái)判斷是否完成請(qǐng)求處理方式

    SpringBoot監(jiān)控Tomcat活動(dòng)線程數(shù)來(lái)判斷是否完成請(qǐng)求處理方式

    這篇文章主要介紹了SpringBoot監(jiān)控Tomcat活動(dòng)線程數(shù)來(lái)判斷是否完成請(qǐng)求處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Springboot整合Redis的詳細(xì)教程分享

    Springboot整合Redis的詳細(xì)教程分享

    這篇文章主要為大家詳細(xì)介紹了如何利用SpringBoot整合Redis,文中的示例代碼講解詳細(xì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助
    2022-08-08
  • 關(guān)于fastjson的常見(jiàn)API詳解

    關(guān)于fastjson的常見(jiàn)API詳解

    這篇文章主要介紹了關(guān)于fastjson的常見(jiàn)API詳解,Fastjson是一個(gè)Java庫(kù),可用于將Java對(duì)象轉(zhuǎn)換為其JSON表示,它還可用于將JSON字符串轉(zhuǎn)換為等效的Java對(duì)象,Fastjson可以處理任意Java對(duì)象,包括您沒(méi)有源代碼的預(yù)先存在的對(duì)象,需要的朋友可以參考下
    2023-07-07
  • Spring Boot中使用Spring-Retry重試框架的實(shí)現(xiàn)

    Spring Boot中使用Spring-Retry重試框架的實(shí)現(xiàn)

    本文主要介紹了Spring Boot中使用Spring-Retry重試框架的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • JVM自定義類(lèi)加載器在代碼擴(kuò)展性實(shí)踐分享

    JVM自定義類(lèi)加載器在代碼擴(kuò)展性實(shí)踐分享

    這篇文章主要介紹了JVM自定義類(lèi)加載器在代碼擴(kuò)展性實(shí)踐分享,一個(gè)類(lèi)型從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,它的整個(gè)生命周期將會(huì)經(jīng)歷加載、驗(yàn)證、準(zhǔn)備、解析、初始化 、使用和卸載七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱(chēng)為連接
    2022-06-06
  • 解決springboot中自定義JavaBean返回的json對(duì)象屬性名稱(chēng)大寫(xiě)變小寫(xiě)問(wèn)題

    解決springboot中自定義JavaBean返回的json對(duì)象屬性名稱(chēng)大寫(xiě)變小寫(xiě)問(wèn)題

    開(kāi)發(fā)過(guò)程中發(fā)現(xiàn)查詢(xún)返回的數(shù)據(jù)出現(xiàn)自定義的JavaBean的屬性值大小寫(xiě)格式出現(xiàn)問(wèn)題,導(dǎo)致前端無(wú)法接受到數(shù)據(jù),目前有四種解決方法,根據(jù)大佬的經(jīng)驗(yàn)之談,前兩種是最簡(jiǎn)單便捷的,后兩種是比較通用的方法,需要的朋友可以參考下
    2023-10-10
  • Java框架Quartz中API、Jobs和Trigger詳解

    Java框架Quartz中API、Jobs和Trigger詳解

    這篇文章主要介紹了Java框架Quartz中API、Jobs和Trigger詳解,JobDetail?對(duì)象是在將?job?加入?scheduler?時(shí),由客戶(hù)端程序(你的程序)創(chuàng)建的,它包含?job?的各種屬性設(shè)置,以及用于存儲(chǔ)?job?實(shí)例狀態(tài)信息的?JobDataMap,需要的朋友可以參考下
    2023-11-11
  • 一鍵打包壓縮,Java項(xiàng)目變身JAR

    一鍵打包壓縮,Java項(xiàng)目變身JAR

    想要一鍵打包Java項(xiàng)目生成JAR文件并進(jìn)行壓縮?本指南將帶你輕松駕馭這項(xiàng)看似復(fù)雜的任務(wù),讓我們一起揭開(kāi)神秘的面紗,輕松打包,高效出發(fā)!
    2023-12-12
  • Springboot JPA打印SQL語(yǔ)句及參數(shù)的實(shí)現(xiàn)

    Springboot JPA打印SQL語(yǔ)句及參數(shù)的實(shí)現(xiàn)

    在SpringBoot項(xiàng)目中調(diào)試和優(yōu)化數(shù)據(jù)庫(kù)操作是很常見(jiàn)的需求,本文主要介紹了Springboot JPA打印SQL語(yǔ)句及參數(shù)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-06-06
  • 關(guān)于Spring Cloud健康檢查的陷阱

    關(guān)于Spring Cloud健康檢查的陷阱

    這篇文章主要介紹了關(guān)于Spring Cloud健康檢查的陷阱,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評(píng)論

滨州市| 三门县| 乌鲁木齐市| 商南县| 六安市| 三门县| 榆树市| 个旧市| 滕州市| 连平县| 运城市| 洛浦县| 三台县| 宣城市| 正阳县| 女性| 阿城市| 西乌| 宣化县| 福安市| 琼中| 巍山| 麟游县| 江北区| 凯里市| 德昌县| 老河口市| 汉中市| 阜康市| 石嘴山市| 金阳县| 德安县| 镇远县| 青岛市| 织金县| 池州市| 怀柔区| 天镇县| 察隅县| 民勤县| 南靖县|