面試官問線程安全的List,看完不再怕了!

最近在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 開始加入,你要能說得上這兩個才能讓面試官信服。

CopyOnWriteArrayList

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

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),轉載請原樣保留本信息。

相關文章
相關標籤/搜索