天天看點

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

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

最近在Java技術棧知識星球裡面有球友問到了線程安全的 List:

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

下面給大家總結一下吧,棧長在之前的文章《出場率比較高的一道多線程安全面試題》裡面也講過 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的部分方法源碼如下:

1.      
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這種集合性能非常差,還有沒有更合适的方案?

介紹兩個并發包裡面的并發集合類:

java.util.concurrent.CopyOnWriteArrayList

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 幹貨,請點選左下角閱讀原文連結加入棧長的知識星球,和我一起學Java!

最近幹貨分享