<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,這幾點一定要注意!

          共 10945字,需瀏覽 22分鐘

           ·

          2021-08-01 10:04

          微信搜索逆鋒起筆關(guān)注后回復編程pdf
          領(lǐng)取編程大佬們所推薦的 23 種編程資料!

          作者:雪山上的蒲公英

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

          ArrayList 不是線程安全的,這點很多人都知道,但是線程不安全的原因及表現(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 <tt>true</tt> (as specified by {@link Collection#add})
               */

              public boolean add(E e) {
                // 判斷列表的capacity容量是否足夠,是否需要擴容
                  ensureCapacityInternal(size + 1);  // Increments modCount!!
                  // 將元素添加進列表的元素數(shù)組里面
                elementData[size++] = e;
                  return true;
              }
          源碼中涉及的幾個元素及方法定義如下:
          /**
               * Default initial capacity.
               */

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

              transient Object[] elementData; 

              /**
             *列表大小,elementData中存儲的元素個數(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的實現(xiàn)主要就是用了一個Object的數(shù)組,用來保存所有的元素,以及一個size變量用來保存當前數(shù)組中已經(jīng)添加了多少元素。
          執(zhí)行add方法時,主要分為兩步:
          • 首先判斷elementData數(shù)組容量是否滿足需求——》判斷如果將當前的新元素加到列表后面,列表的elementData數(shù)組的大小是否滿足,如果size + 1的這個需求長度大于了elementData這個數(shù)組的長度,那么就要對這個數(shù)組進行擴容;
          • 之后在elementData對應位置上設(shè)置元素的值。

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

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

          由于ArrayList添加元素是如上面分兩步進行,可以看出第一個不安全的隱患,在多個線程進行add操作時可能會導致elementData數(shù)組越界。
          具體邏輯如下:
          1. 列表大小為9,即size=9
          2. 線程A開始進入add方法,這時它獲取到size的值為9,調(diào)用ensureCapacityInternal方法進行容量判斷。
          3. 線程B此時也進入add方法,它獲取到size的值也為9,也開始調(diào)用ensureCapacityInternal方法。
          4. 線程A發(fā)現(xiàn)需求大小為10,而elementData的大小就為10,可以容納。于是它不再擴容,返回。
          5. 線程B也發(fā)現(xiàn)需求大小為10,也可以容納,返回。
          6. 線程A開始進行設(shè)置值操作, elementData[size++] = e 操作。此時size變?yōu)?0。
          7. 線程B也開始進行設(shè)置值操作,它嘗試設(shè)置elementData[10] = e,而elementData沒有進行過擴容,它的下標最大為9。于是此時會報出一個數(shù)組越界的異常ArrayIndexOutOfBoundsException.

          2.2 元素值覆蓋和為空問題

          elementData[size++] = e 設(shè)置值的操作同樣會導致線程不安全。從這兒可以看出,這步操作也不是一個原子操作,它由如下兩步操作構(gòu)成:
          elementData[size] = e;
          size = size + 1;
          在單線程執(zhí)行這兩條代碼時沒有任何問題,但是當多線程環(huán)境下執(zhí)行時,可能就會發(fā)生一個線程的值覆蓋另一個線程添加的值,具體邏輯如下:
          1. 列表大小為0,即size=0
          2. 線程A開始添加一個元素,值為A。此時它執(zhí)行第一條操作,將A放在了elementData下標為0的位置上。
          3. 接著線程B剛好也要開始添加一個值為B的元素,且走到了第一步操作。此時線程B獲取到size的值依然為0,于是它將B也放在了elementData下標為0的位置上。
          4. 線程A開始將size的值增加為1
          5. 線程B開始將size的值增加為2
          這樣線程AB執(zhí)行完畢后,理想中情況為size為2,elementData下標0的位置為A,下標1的位置為B。而實際情況變成了size為2,elementData下標為0的位置變成了B,下標1的位置上什么都沒有。并且后續(xù)除非使用set方法修改此位置的值,否則將一直為null,因為size為2,添加元素時會從下標為2的位置上開始。

          3. 代碼示例

          如下,通過兩個線程對ArrayList添加元素,復現(xiàn)上面的兩種不安全情況。
          import java.util.ArrayList;
          import java.util.List;

          public class ArrayListSafeTest {

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

                  final List<Integer> list = new ArrayList<Integer>();
                  // 線程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) + "個元素為:" + list.get(i));
                  }
              }
          }
          執(zhí)行過程中,兩種情況出現(xiàn)如下:

          4. ArrayList線程安全處理

          4.1 Collections.synchronizedList

          最常用的方法是通過 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封裝變量,相當于把變量丟進執(zhí)行線程中去,每new一個新的線程,變量也會new一次,一定程度上會造成性能[內(nèi)存]損耗,但其執(zhí)行完畢就銷毀的機制使得ThreadLocal變成比較優(yōu)化的并發(fā)解決方案)。
          ThreadLocal<List<Object>> threadList = new ThreadLocal<List<Object>>() {
              @Override
              protected List<Object> initialValue() {
                    return new ArrayList<Object>();
              }
          };

          參考

          • blog.csdn.net/u012859681/article/details/78206494

          • www.cnblogs.com/mabaoqing/p/7446938.html


          推薦閱讀:

          在開發(fā)中遇到的一些多線程問題

          偽造郵件釣魚,要知道的秘密!

          IntelliJ idea 高效使用教程,一勞永逸!

          9 本PDF,速來下載,秒刪!

          在 Google 中輸入這 4 個單詞,竟然得到了這個?


          瀏覽 27
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  激情青青草 | 四虎免费看黄 | 无码骚逼日逼T V | 国精产品自偷自偷综合欧美 | 91av在线麻豆 |