<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          多線程場景下使用 ArrayList,這幾點(diǎn)一定要注意!

          共 3559字,需瀏覽 8分鐘

           ·

          2021-02-04 10:20

          點(diǎn)擊上方藍(lán)色“小哈學(xué)Java”,選擇“設(shè)為星標(biāo)

          回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!

          作者:雪山上的蒲公英

          www.cnblogs.com/zjfjava/p/10217720.html


          ArrayList 不是線程安全的,這點(diǎn)很多人都知道,但是線程不安全的原因及表現(xiàn),怎么在多線程情況下使用ArrayList,可能不是很清楚,這里總結(jié)一下。

          1. 源碼分析

          查看 ArrayList 的 add 操作源碼如下:

          /**
          ?????*?Appends?the?specified?element?to?the?end?of?this?list.
          ?????*
          ?????*?@param?e?element?to?be?appended?to?this?list
          ?????*?@return?true?(as?specified?by?{@link?Collection#add})
          ?????*/

          ????public?boolean?add(E?e)?{
          ??????//?判斷列表的capacity容量是否足夠,是否需要擴(kuò)容
          ????????ensureCapacityInternal(size?+?1);??//?Increments?modCount!!
          ????????//?將元素添加進(jìn)列表的元素?cái)?shù)組里面
          ??????elementData[size++]?=?e;
          ????????return?true;
          ????}

          源碼中涉及的幾個(gè)元素及方法定義如下:

          /**
          ?????*?Default?initial?capacity.
          ?????*/

          ????private?static?final?int?DEFAULT_CAPACITY?=?10;
          ????/**
          ????*?列表元素集合數(shù)組
          ?????*?如果新建ArrayList對(duì)象時(shí)沒有指定大小,那么會(huì)將EMPTY_ELEMENTDATA賦值給elementData,
          ?????*?并在第一次添加元素時(shí),將列表容量設(shè)置為DEFAULT_CAPACITY?
          ???*/

          ????transient?Object[]?elementData;?

          ????/**
          ???*列表大小,elementData中存儲(chǔ)的元素個(gè)數(shù)
          ???*/

          ????private?int?size;

          ???private?void?ensureCapacityInternal(int?minCapacity)?{
          ????????if?(elementData?==?DEFAULTCAPACITY_EMPTY_ELEMENTDATA)?{
          ????????????minCapacity?=?Math.max(DEFAULT_CAPACITY,?minCapacity);
          ????????}

          ????????ensureExplicitCapacity(minCapacity);
          ????}

          ????private?void?ensureExplicitCapacity(int?minCapacity)?{
          ????????modCount++;

          ????????//?overflow-conscious?code
          ????????if?(minCapacity?-?elementData.length?>?0)
          ????????????grow(minCapacity);
          ????}
          ????private?void?grow(int?minCapacity)?{
          ????????//?overflow-conscious?code
          ????????int?oldCapacity?=?elementData.length;
          ????????int?newCapacity?=?oldCapacity?+?(oldCapacity?>>?1);
          ????????if?(newCapacity?-?minCapacity?0)
          ????????????newCapacity?=?minCapacity;
          ????????if?(newCapacity?-?MAX_ARRAY_SIZE?>?0)
          ????????????newCapacity?=?hugeCapacity(minCapacity);
          ????????//?minCapacity?is?usually?close?to?size,?so?this?is?a?win:
          ????????elementData?=?Arrays.copyOf(elementData,?newCapacity);
          ????}

          通過源碼可以看出:ArrayList的實(shí)現(xiàn)主要就是用了一個(gè)Object的數(shù)組,用來保存所有的元素,以及一個(gè)size變量用來保存當(dāng)前數(shù)組中已經(jīng)添加了多少元素。

          執(zhí)行add方法時(shí),主要分為兩步:

          • 首先判斷elementData數(shù)組容量是否滿足需求——》判斷如果將當(dāng)前的新元素加到列表后面,列表的elementData數(shù)組的大小是否滿足,如果size + 1的這個(gè)需求長度大于了elementData這個(gè)數(shù)組的長度,那么就要對(duì)這個(gè)數(shù)組進(jìn)行擴(kuò)容;
          • 之后在elementData對(duì)應(yīng)位置上設(shè)置元素的值。

          2. 線程不安全的兩種體現(xiàn)

          2.1 數(shù)組越界異常 ArrayIndexOutOfBoundsException

          由于ArrayList添加元素是如上面分兩步進(jìn)行,可以看出第一個(gè)不安全的隱患,在多個(gè)線程進(jìn)行add操作時(shí)可能會(huì)導(dǎo)致elementData數(shù)組越界。

          具體邏輯如下:

          1. 列表大小為9,即size=9
          2. 線程A開始進(jìn)入add方法,這時(shí)它獲取到size的值為9,調(diào)用ensureCapacityInternal方法進(jìn)行容量判斷。
          3. 線程B此時(shí)也進(jìn)入add方法,它獲取到size的值也為9,也開始調(diào)用ensureCapacityInternal方法。
          4. 線程A發(fā)現(xiàn)需求大小為10,而elementData的大小就為10,可以容納。于是它不再擴(kuò)容,返回。
          5. 線程B也發(fā)現(xiàn)需求大小為10,也可以容納,返回。
          6. 線程A開始進(jìn)行設(shè)置值操作, elementData[size++] = e 操作。此時(shí)size變?yōu)?0。
          7. 線程B也開始進(jìn)行設(shè)置值操作,它嘗試設(shè)置elementData[10] = e,而elementData沒有進(jìn)行過擴(kuò)容,它的下標(biāo)最大為9。于是此時(shí)會(huì)報(bào)出一個(gè)數(shù)組越界的異常ArrayIndexOutOfBoundsException.

          2.2 元素值覆蓋和為空問題

          elementData[size++] = e 設(shè)置值的操作同樣會(huì)導(dǎo)致線程不安全。從這兒可以看出,這步操作也不是一個(gè)原子操作,它由如下兩步操作構(gòu)成:

          elementData[size]?=?e;
          size?=?size?+?1;

          在單線程執(zhí)行這兩條代碼時(shí)沒有任何問題,但是當(dāng)多線程環(huán)境下執(zhí)行時(shí),可能就會(huì)發(fā)生一個(gè)線程的值覆蓋另一個(gè)線程添加的值,具體邏輯如下:

          1. 列表大小為0,即size=0
          2. 線程A開始添加一個(gè)元素,值為A。此時(shí)它執(zhí)行第一條操作,將A放在了elementData下標(biāo)為0的位置上。
          3. 接著線程B剛好也要開始添加一個(gè)值為B的元素,且走到了第一步操作。此時(shí)線程B獲取到size的值依然為0,于是它將B也放在了elementData下標(biāo)為0的位置上。
          4. 線程A開始將size的值增加為1
          5. 線程B開始將size的值增加為2

          這樣線程AB執(zhí)行完畢后,理想中情況為size為2,elementData下標(biāo)0的位置為A,下標(biāo)1的位置為B。而實(shí)際情況變成了size為2,elementData下標(biāo)為0的位置變成了B,下標(biāo)1的位置上什么都沒有。并且后續(xù)除非使用set方法修改此位置的值,否則將一直為null,因?yàn)閟ize為2,添加元素時(shí)會(huì)從下標(biāo)為2的位置上開始。

          3. 代碼示例

          如下,通過兩個(gè)線程對(duì)ArrayList添加元素,復(fù)現(xiàn)上面的兩種不安全情況。

          import?java.util.ArrayList;
          import?java.util.List;

          public?class?ArrayListSafeTest?{

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{

          ????????final?List?list?=?new?ArrayList();
          ????????//?線程A將1-1000添加到列表
          ????????new?Thread(new?Runnable()?{

          ????????????@Override
          ????????????public?void?run()?{
          ????????????????for?(int?i?=?1;?i?1000;?i++)?{
          ????????????????????list.add(i);

          ????????????????????try?{
          ????????????????????????Thread.sleep(1);
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????e.printStackTrace();
          ????????????????????}
          ????????????????}

          ????????????}

          ????????}).start();
          ????????
          ????????//?線程B將1001-2000添加到列表
          ????????new?Thread(new?Runnable()?{

          ????????????@Override
          ????????????public?void?run()?{
          ????????????????for?(int?i?=?1001;?i?2000;?i++)?{
          ????????????????????list.add(i);

          ????????????????????try?{
          ????????????????????????Thread.sleep(1);?
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????e.printStackTrace();
          ????????????????????}
          ????????????????}

          ????????????}

          ????????}).start();
          ????????
          ????????Thread.sleep(1000);

          ????????//?打印所有結(jié)果
          ????????for?(int?i?=?0;?i?????????????System.out.println("第"?+?(i?+?1)?+?"個(gè)元素為:"?+?list.get(i));
          ????????}
          ????}
          }

          執(zhí)行過程中,兩種情況出現(xiàn)如下:

          img
          img

          4. ArrayList線程安全處理

          4.1 Collections.synchronizedList

          最常用的方法是通過 Collections 的 synchronizedList 方法將 ArrayList 轉(zhuǎn)換成線程安全的容器后再使用。

          List?list?=Collections.synchronizedList(new?ArrayList);

          4.2 為list.add()方法加鎖

          synchronized(list.get())?{
          list.get().add(model);
          }

          4.3 CopyOnWriteArrayList

          使用線程安全的 CopyOnWriteArrayList 代替線程不安全的 ArrayList。

          List?list1?=?new?CopyOnWriteArrayList();

          4.4 使用ThreadLocal

          使用ThreadLocal變量確保線程封閉性(封閉線程往往是比較安全的, 但由于使用ThreadLocal封裝變量,相當(dāng)于把變量丟進(jìn)執(zhí)行線程中去,每new一個(gè)新的線程,變量也會(huì)new一次,一定程度上會(huì)造成性能[內(nèi)存]損耗,但其執(zhí)行完畢就銷毀的機(jī)制使得ThreadLocal變成比較優(yōu)化的并發(fā)解決方案)。

          ThreadLocal>?threadList?=?new?ThreadLocal>()?{
          ????@Override
          ????protected?List?initialValue()?{
          ??????????return?new?ArrayList();
          ????}
          };

          參考

          • blog.csdn.net/u012859681/article/details/78206494
          • www.cnblogs.com/mabaoqing/p/7446938.html

          END


          有熱門推薦??

          1.?MyBatis 的執(zhí)行流程,寫得太好了叭!

          2.?監(jiān)控、鏈路追蹤、日志的區(qū)別,傻傻分不清?

          3.?你還在用命令看日志?快用 Kibana 吧,一張圖片勝過千萬行日志!

          4.?Redis為什么變慢了?一文講透如何排查Redis性能問題 | 萬字長文

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    一区二区成人电影 | 操逼视频免费试看 | 午夜高清 | 操生在线视频 | 操逼网站无需下载在线观看 |