<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>

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

          共 5133字,需瀏覽 11分鐘

           ·

          2021-02-26 07:43

          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í)沒(méi)有指定大小,那么會(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); ? ?}



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


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


          • 首先判斷elementData數(shù)組容量是否滿足需求——》判斷如果將當(dāng)前的新元素加到列表后面,列表的elementData數(shù)組的大小是否滿足,如果size + 1的這個(gè)需求長(zhǎng)度大于了elementData這個(gè)數(shù)組的長(zhǎng)度,那么就要對(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開(kāi)始進(jìn)入add方法,這時(shí)它獲取到size的值為9,調(diào)用ensureCapacityInternal方法進(jìn)行容量判斷。

          3. 線程B此時(shí)也進(jìn)入add方法,它獲取到size的值也為9,也開(kāi)始調(diào)用ensureCapacityInternal方法。

          4. 線程A發(fā)現(xiàn)需求大小為10,而elementData的大小就為10,可以容納。于是它不再擴(kuò)容,返回。

          5. 線程B也發(fā)現(xiàn)需求大小為10,也可以容納,返回。

          6. 線程A開(kāi)始進(jìn)行設(shè)置值操作, elementData[size++] = e 操作。此時(shí)size變?yōu)?0。

          7. 線程B也開(kāi)始進(jìn)行設(shè)置值操作,它嘗試設(shè)置elementData[10] = e,而elementData沒(méi)有進(jìn)行過(guò)擴(kuò)容,它的下標(biāo)最大為9。于是此時(shí)會(huì)報(bào)出一個(gè)數(shù)組越界的異常ArrayIndexOutOfBoundsException.


          2.2 元素值覆蓋和為空問(wèn)題


          elementData[size++] = e 設(shè)置值的操作同樣會(huì)導(dǎo)致線程不安全。


          從這兒可以看出,這步操作也不是一個(gè)原子操作,它由如下兩步操作構(gòu)成:



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


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


          1. 列表大小為0,即size=0

          2. 線程A開(kāi)始添加一個(gè)元素,值為A。此時(shí)它執(zhí)行第一條操作,將A放在了elementData下標(biāo)為0的位置上。

          3. 接著線程B剛好也要開(kāi)始添加一個(gè)值為B的元素,且走到了第一步操作。此時(shí)線程B獲取到size的值依然為0,于是它將B也放在了elementData下標(biāo)為0的位置上。

          4. 線程A開(kāi)始將size的值增加為1

          5. 線程B開(kāi)始將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的位置上什么都沒(méi)有。


          并且后續(xù)除非使用set方法修改此位置的值,否則將一直為null,因?yàn)閟ize為2,添加元素時(shí)會(huì)從下標(biāo)為2的位置上開(kāi)始。


          3. 代碼示例


          如下,通過(guò)兩個(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 Listlist = 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 < list.size(); i++) { ? ? ? ? ? ?System.out.println("第" + (i + 1) + "個(gè)元素為:" + list.get(i)); ? ? ? ?} ? ?}}


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



          4. ArrayList線程安全處理


          4.1 Collections.synchronizedList


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



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


          4.2 為list.add()方法加鎖



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


          4.3?CopyOnWriteArrayList


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



          List<Object> list1 = new CopyOnWriteArrayList<Object>();


          4.4?使用ThreadLocal


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



          ThreadLocalObject>> threadList = new ThreadLocalObject>>() { ? ?@Override ? ?protected List<Object> initialValue() { ? ? ? ? ?return new ArrayList<Object>(); ? ?}};
          瀏覽 42
          點(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>
                  国产一级a毛一级做a爱 | 青青草成人无码视频 | 日韩无码人妻 | 黄色视频图片免费看 | 免费无码操逼 |