
最近在Java技術棧知識星球裡面有球友問到了線程安全的 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!
最近幹貨分享