最近在Java技術棧知識星球裏面有球友問到了線程安全的 List:java
掃碼查看答案或加入知識星球面試
棧長在以前的文章《出場率比較高的一道多線程安全面試題》裏面講過 ArrayList 的不安全性。安全
那麼面試官會問你,既然 ArrayList 是線程不安全的,怎麼保證它的線程安全性呢?或者有什麼替代方案?微信
往下看,看我如何碾壓他!多線程
大部分人會脫口而出:用Vector,這樣只會讓面試官鄙視!除了Vector,你還會別的嗎?併發
你至少還得說得上這種:高併發
java.util.Collections.SynchronizedList
它能把全部 List 接口的實現類轉換成線程安全的List,比 Vector 有更好的擴展性和兼容性,SynchronizedList的構造方法以下:性能
final List<E> list; SynchronizedList(List<E> list) { super(list); this.list = list; }
SynchronizedList的部分方法源碼以下:this
public E get(int index) { synchronized (mutex) {return list.get(index);} } public E set(int index, E element) { synchronized (mutex) {return list.set(index, element);} } public void add(int index, E element) { synchronized (mutex) {list.add(index, element);} } public E remove(int index) { synchronized (mutex) {return list.remove(index);} }
很惋惜,它全部方法都是帶同步對象鎖的,和 Vector 同樣,它不是性能最優的。即便你能說到這裏,面試官還會繼續往下追問,好比在讀多寫少的狀況,SynchronizedList這種集合性能很是差,還有沒有更合適的方案?spa
介紹兩個併發包裏面的併發集合類:
java.util.concurrent.CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArraySet
CopyOnWrite集合類也就這兩個,Java 1.5 開始加入,你要能說得上這兩個才能讓面試官信服。
CopyOnWrite(簡稱:COW):即複製再寫入,就是在添加元素的時候,先把原 List 列表複製一份,再添加新的元素。
先來看下它的 add 方法源碼:
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(); } }
添加元素時,先加鎖,再進行復制替換操做,最後再釋放鎖。
再來看下它的 get 方法源碼:
private E get(Object[] a, int index) { return (E) a[index]; } public E get(int index) { return get(getArray(), index); }
能夠看到,獲取元素並無加鎖。
這樣作的好處是,在高併發狀況下,讀取元素時就不用加鎖,寫數據時才加鎖,大大提高了讀取性能。
CopyOnWriteArraySet邏輯就更簡單了,就是使用 CopyOnWriteArrayList 的 addIfAbsent 方法來去重的,添加元素的時候判斷對象是否已經存在,不存在才添加進集合。
/** * Appends the element, if not present. * * @param e element to be added to this list, if absent * @return {@code true} if the element was added */ public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); }
這兩種併發集合,雖然牛逼,但只適合於讀多寫少的狀況,若是寫多讀少,使用這個就沒意義了,由於每次寫操做都要進行集合內存複製,性能開銷很大,若是集合較大,很容易形成內存溢出。
下次面試官問你線程安全的 List,你能夠從 Vector > SynchronizedList > CopyOnWriteArrayList 這樣的順序依次說上來,這樣纔有帶入感,也能體現你對知識點的掌握程度。
看完有沒有收穫呢?下次面試應該能秒殺面試官了吧!
你們也能夠關注微信公衆號:Java技術棧,棧長將繼續分享更多多線程在工做中的實戰用法,請關注後續文章,或者在公衆號後臺回覆:多線程,棧長已經寫了好多多線程文章,都是接地氣乾貨。
以爲有用,轉發分享下朋友圈給更多的人看吧~
本文原創首發於微信公衆號:Java技術棧(id:javastack),轉載請原樣保留本信息。