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

          因為不知道Java的CopyOnWriteArrayList,面試官讓我回去等通知

          共 8498字,需瀏覽 17分鐘

           ·

          2020-07-29 13:21

          hello,同學們,大家好,我是沉默王二,在我為數不多的面試經歷中,有一位姓馬的面試官令我印象深刻,九年過去了,我還能記得他為數不多的發(fā)量。

          老馬:“兄弟,ArrayList 是線程安全的嗎?”
          王二:“不是啊。”
          老馬:“那有沒有線程安全的 List?”
          王二:“有啊,Vector。”
          老馬:“還有別的嗎?”
          王二:“Vector 不就夠用了嗎?”
          老馬看了一下左手腕上的表,說道:“今天差不多就到這里吧,你回去等通知。”

          (不是,我特么不是剛進來,就回答了三個問題而已,就到這了?)

          現在回想起來當時一臉懵逼的樣子,臉上情不自禁地泛起了紅暈,老馬的意思是讓我說說 Java 的 CopyOnWriteArrayList,可惜我當時幾乎沒怎么用過這個類,也不知道它就是個線程安全的 List,慚愧啊慚愧。

          (地上有坑嗎?我想跳進去。)

          真正的勇士敢于直面過去的慘淡,經過這么多年的努力,我的技術功底已經大有長進了,是時候輸出一波傷害了。希望這篇文章能夠給不太了解 CopyOnWriteArrayList 的同學一點點幫助,到時候給面試官一個好看。

          注:我用的是 OpenJDK 14。

          01、Vector

          Vector 的源碼文檔上直截了當地說了,“如果不需要線程安全,推薦使用 ArrayList 替代 Vector。”說實話,在我十多年的編程生涯中,的確很少使用 Vector,因為它的線程安全是建立在每個方法上都加了 synchronized 關鍵字的基礎上,鎖的粒度很高,意味著性能就不咋滴。

          public?synchronized?boolean?add(E?e)?{
          ????modCount++;
          ????add(e,?elementData,?elementCount);
          ????return?true;
          }

          public?synchronized?E?remove(int?index)?{
          ????modCount++;
          ????if?(index?>=?elementCount)
          ????????throw?new?ArrayIndexOutOfBoundsException(index);
          ????E?oldValue?=?elementData(index);

          ????int?numMoved?=?elementCount?-?index?-?1;
          ????if?(numMoved?>?0)
          ????????System.arraycopy(elementData,?index+1,?elementData,?index,
          ????????????????numMoved);
          ????elementData[--elementCount]?=?null;?//?Let?gc?do?its?work

          ????return?oldValue;
          }

          就連 size() 這樣的方法上都加了 synchronized,可想而知,Vector 有多鋪張浪費,有多錦衣玉食。

          如果對 synchronized 關鍵字不太了解的話,可以點擊下面的鏈接查看我之前寫的一篇文章。

          我去,你竟然還不會用 synchronized

          高并發(fā)的情況下,一般都要求性能要給力,Vector 顯然不夠格,所以被遺忘在角落也是“罪有應得”啊。

          02、SynchronizedList

          那有些同學可能會說,可以使用 Collections.synchronizedList() 讓 ArrayList 變成線程安全啊。

          public?static??List?synchronizedList(List?list)?{
          ????return?(list?instanceof?RandomAccess??
          ????????????new?Collections.SynchronizedRandomAccessList<>(list)?:
          ????????????new?Collections.SynchronizedList<>(list));
          }

          無論是 SynchronizedRandomAccessList 還是 SynchronizedList,它們都沒有在方法級別上使用 synchronized 關鍵字,而是在方法體內使用了 synchronized(this) 塊。

          public?void?add(int?index,?E?element)?{
          ????synchronized?(mutex)?{list.add(index,?element);}
          }
          public?E?remove(int?index)?{
          ????synchronized?(mutex)?{return?list.remove(index);}
          }

          其中 mutex 為 this 關鍵字,也就是當前對象。

          final?Object?mutex;?????//?Object?on?which?to?synchronize

          SynchronizedCollection(Collection?c)?{
          ????this.c?=?Objects.requireNonNull(c);
          ????mutex?=?this;
          }

          03、ConcurrentModificationException

          ConcurrentModificationException 這個異常不知道同學們有沒有遇到過?我先來敲段代碼讓它發(fā)生一次,讓同學們認識一下。

          List?list?=?new?ArrayList<>();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");

          for?(String?str?:?list)?{
          ????if?("沉默王二".equals(str))?{
          ????????list.remove(str);
          ????}
          }

          System.out.println(list);

          運行這段代碼就會拋出 ConcurrentModificationException:

          Exception?in?thread?"main"?java.util.ConcurrentModificationException
          ????at?java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1012)
          ????at?java.base/java.util.ArrayList$Itr.next(ArrayList.java:966)

          通過異常的堆棧信息可以查找到,異常發(fā)生在 ArrayList 的內部類 Itr 的 checkForComodification() 方法中。

          final?void?checkForComodification()?{
          ????if?(modCount?!=?expectedModCount)
          ????????throw?new?ConcurrentModificationException();
          }

          也就是說,在執(zhí)行 checkForComodification() 方法的時候,發(fā)現 modCount 和 expectedModCount 不等,就拋出了 ConcurrentModificationException 異常。

          為什么會這樣呢?之前的代碼也沒有調用 checkForComodification() 方法啊!

          那就只能來看一下反編譯后的字節(jié)碼了,原來 for-each 這個語法糖是通過 Iterator 實現的。

          List?list?=?new?ArrayList();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");
          Iterator?var3?=?list.iterator();

          while?(var3.hasNext())?{
          ????String?str?=?(String)?var3.next();
          ????if?("沉默王二".equals(str))?{
          ????????list.remove(str);
          ????}
          }

          System.out.println(list);

          在執(zhí)行 list.iterator() 的時候,其實返回的就是 ArrayList 的內部類 Itr。

          public?Iterator?iterator()?{
          ????return?new?ArrayList.Itr();
          }

          迭代器 Iterator 是 fail-fast 的,如果以任何方式(包括 remove 和
          add)對迭代器進行修改的話,就會拋出 ConcurrentModificationException。

          迭代器在執(zhí)行 remove() 方法的時候,會對 modCount 加 1。remove() 方法內部會調用 fastRemove() 方法。

          private?void?fastRemove(Object[]?es,?int?i)?{
          ????modCount++;
          ????final?int?newSize;
          ????if?((newSize?=?size?-?1)?>?i)
          ????????System.arraycopy(es,?i?+?1,?es,?i,?newSize?-?i);
          ????es[size?=?newSize]?=?null;
          }

          當在進行下一次 next() 會執(zhí)行 checkForComodification() 方法,結果發(fā)現 modCount 為 4,而 expectedModCount 為 3,于是就拋出了異常。

          之所以在單線程的情況下就拋出 ConcurrentModificationException,就是為了在多線程并發(fā)的情況下,不冒任何的危險,提前規(guī)避掉其他線程對 List 修改的可能性。

          ArrayList 返回的迭代器是 fail-fast 的,Vector 的也是,SynchronizedList 的也是。這就意味著它們在多線程環(huán)境下通過 for-each 遍歷進行增刪操作的時候會出問題。

          04、CopyOnWriteArrayList

          瞧,為了引出 CopyOnWriteArrayList,我花了多少心思。

          List?list?=?new?CopyOnWriteArrayList();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");

          for?(String?str?:?list)?{
          ????if?("沉默王二".equals(str))?{
          ????????list.remove(str);
          ????}
          }

          System.out.println(list);

          把 ArrayList 換成 CopyOnWriteArrayList,程序就能夠正常執(zhí)行了,輸出結果如下所示。

          [沉默王三,?一個文章真特么有趣的程序員]

          之所以不拋出 ConcurrentModificationException 異常,是因為 CopyOnWriteArrayList 是 fail-safe 的,迭代器遍歷的是原有的數組,remove 的時候 remove 的是復制后的新數組,然后再將新數組賦值給原有的數組。

          不過,任何在獲取迭代器之后對 CopyOnWriteArrayList 的修改將不會及時反映迭代器里。

          CopyOnWriteArrayList?list1?=
          ????????new?CopyOnWriteArrayList<>(new?String[]?{"沉默王二",?"沉默王三"});
          Iterator?itr?=?list1.iterator();
          list1.add("沉默王四");
          while(itr.hasNext())?{
          ????System.out.print(itr.next()?+?"?");
          }

          沉默王四并不會出現在輸出結果中。

          沉默王二?沉默王三?

          ArrayList 的迭代器 Itr 是支持 remove 的。

          List?list?=?new?ArrayList();
          list.add("沉默王二");
          list.add("沉默王三");
          list.add("一個文章真特么有趣的程序員");
          Iterator?var3?=?list.iterator();

          while?(var3.hasNext())?{
          ????String?str?=?(String)?var3.next();
          ????if?("沉默王二".equals(str))?{
          ????????var3.remove();
          ????}
          }

          System.out.println(list);

          程序輸出的結果如下所示:

          [沉默王三,?一個文章真特么有趣的程序員]

          而 CopyOnWriteArrayList 的迭代器 COWIterator 是不支持 remove 的。

          public?void?remove()?{
          ????????????throw?new?UnsupportedOperationException();
          ????????}

          CopyOnWriteArrayList 實現了 List 接口,不過,它不在 java.util 包下,而在 java.util.concurrent 包下,算作是 ArrayList 的增強版,線程安全的。

          顧名思義,CopyOnWriteArrayList 在進行寫操作(add、set、remove)的時候會先進行拷貝,底層是通過數組復制來實現的。

          Java 8 的時候,CopyOnWriteArrayList 的增刪改操作方法使用的是 ?ReentrantLock(可重入鎖,一個線程獲得了鎖之后仍然可以反復的加鎖,不會出現自己阻塞自己的情況)。

          public?boolean?add(E?e)?{
          ????final?ReentrantLock?lock?=?this.lock;
          ????lock.lock();
          ????try?{
          ????????Object[]?elements?=?getArray();
          ????????int?len?=?elements.length;
          ????????Object[]?newElements?=?Arrays.copyOf(elements,?len?+?1);
          ????????newElements[len]?=?e;
          ????????setArray(newElements);
          ????????return?true;
          ????}?finally?{
          ????????lock.unlock();
          ????}
          }

          Java 14 的時候,已經改成 synchronized 塊了。

          public?boolean?add(E?e)?{
          ????synchronized?(lock)?{
          ????????Object[]?es?=?getArray();
          ????????int?len?=?es.length;
          ????????es?=?Arrays.copyOf(es,?len?+?1);
          ????????es[len]?=?e;
          ????????setArray(es);
          ????????return?true;
          ????}
          }

          其中的 lock 是一個 Object 對象(注釋上說和 ReentrantLock 有一點關系)。

          /**
          ?*?The?lock?protecting?all?mutators.??(We?have?a?mild?preference
          ?*?for?builtin?monitors?over?ReentrantLock?when?either?will?do.)
          ?*/

          final?transient?Object?lock?=?new?Object();

          使用 ReentrantLock 性能更好,還是 synchronized 塊性能更好,同學們可以試驗一下。不過,從另外一些細節(jié)上看,Java 14 的寫法比 Java 8 更簡潔一些,其中就少了一個 newElements 變量的創(chuàng)建。

          再來看 set() 方法:

          public?E?set(int?index,?E?element)?{
          ????synchronized?(lock)?{
          ????????Object[]?es?=?getArray();
          ????????E?oldValue?=?elementAt(es,?index);

          ????????if?(oldValue?!=?element)?{
          ????????????es?=?es.clone();
          ????????????es[index]?=?element;
          ????????}
          ????????//?Ensure?volatile?write?semantics?even?when?oldvalue?==?element
          ????????setArray(es);
          ????????return?oldValue;
          ????}
          }

          同樣使用了 synchronized 塊,并且調用了封裝好的 clone() 方法進行了復制。

          然后來看 remove() 方法:

          public?E?remove(int?index)?{
          ????synchronized?(lock)?{
          ????????Object[]?es?=?getArray();
          ????????int?len?=?es.length;
          ????????E?oldValue?=?elementAt(es,?index);
          ????????int?numMoved?=?len?-?index?-?1;
          ????????Object[]?newElements;
          ????????if?(numMoved?==?0)
          ????????????newElements?=?Arrays.copyOf(es,?len?-?1);
          ????????else?{
          ????????????newElements?=?new?Object[len?-?1];
          ????????????System.arraycopy(es,?0,?newElements,?0,?index);
          ????????????System.arraycopy(es,?index?+?1,?newElements,?index,
          ????????????????????numMoved);
          ????????}
          ????????setArray(newElements);
          ????????return?oldValue;
          ????}
          }

          synchronized 塊是必須的,數組復制(System.arraycopy())也是必須的。

          和 Vector 不同的是,CopyOnWriteArrayList 的 get()size() 方法不再加鎖。

          public?int?size()?{
          ????return?getArray().length;
          }

          public?E?get(int?index)?{
          ????return?elementAt(getArray(),?index);
          }

          簡單總結一下就是:第一,CopyOnWriteArrayList 在修改時,復制出一個新數組,修改的操作在新數組中完成,最后將新數組賦值給原有的數組引用。第二,CopyOnWriteArrayList 的寫加鎖,讀不加鎖。

          CopyOnWriteArrayList 有很多優(yōu)勢,但數組復制是沉重的,如果寫的操作比較多,而讀的操作比較少,內存就會被占用得比較多;另外,CopyOnWriteArrayList 無法保證數據是實時同步的,因為讀寫操作是分離的,寫的操作都建立在復制的新數組上,而讀的是原有的數組。

          05、最后

          如果九年前,我就看到了這樣一篇文章,一定就不會被老馬刁難呢,保不準還能再拖延半個小時,讓他多問二十個問題。但我想同學們一定是比我幸運的,至少現在看到了,不晚,對不對?

          ------------------

          公眾號:沉默王二(ID:cmower)
          CSDN:沉默王二
          這是一個有顏值卻靠才華吃飯的程序員,你知道,他的文章風趣幽默,讀起來就好像花錢一樣爽快。

          長按下圖二維碼關注,你將感受到一個有趣的靈魂,且每篇文章都有干貨。

          ------------------


          原創(chuàng)不易,莫要白票,如果覺得有點用的話,請毫不留情地素質三連吧,分享、點贊、在看,我不挑,因為這將是我寫作更多優(yōu)質文章的最強動力。

          瀏覽 48
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  五月色婷 | 91在线看片 | 午夜久久久久久久久久影院 | 国产豆花视频在线 | 成人精品三级AV在线看 |