1.使用SynchronizedList
SynchronizedList是一個線程安全的包裝類。繼承于SynchronizedCollection,SynchronizedCollection實現了Collection接口,SynchronizedList包含一個List對象,對List的訪問修改方法進行了一些封裝,在封裝的方法中會對list使用同步鎖加鎖,然后再進行存取和修改操作。
使用方法如下
LinkedList linkedList = new LinkedList();
//調用Collections的synchronizedList方法,傳入一個linkedList,會返回一個SynchronizedList實例對象
List synchronizedList = Collections.synchronizedList(linkedList);
//調用Collections的synchronizedList方法,ArrayList,返回一個SynchronizedRandomAccessList實例對象
ArrayList arrayList = new ArrayList();
List synchronizedRandomAccessList = Collections.synchronizedList(linkedList);
(Collections.synchronizedList()方法會判斷傳入的對象是否實現了 RandomAccess接口,是的話,會返回一個SynchronizedRandomAccessList對象,SynchronizedRandomAccessList是SynchronizedList的子類,只是會多一個以線程安全的方式獲取子數組的方法)。
SynchronizedList類的部分代碼如下:
static class SynchronizedList
extends SynchronizedCollection
implements List {
final List list;//源list
final Object mutex;
SynchronizedCollection(Collection c) {
this.c = Objects.requireNonNull(c);
mutex = this;//mutex就是SynchronizedList實例自己,作為同步鎖使用
}
public E get(int index) {
synchronized (mutex) {
是父類中的成員變量,在父類中會將list賦值給mutex
return list.get(index);
}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
}
2.使用CopyOnWriteArrayList
CopyOnWriteArrayList跟ArrayList類似,都是實現了List接口,只不過它的父類是Object,而不是AbstractList。CopyOnWriteArrayList與ArrayList的不同在于,
1.內部持有一個ReentrantLock類型的lock成員變量,
final transient ReentrantLock lock = new ReentrantLock();
在對數組進行修改的方法中,都會先獲取lock,獲取成功才能進行修改,修改完釋放鎖,保證每次只允許一個線程對數組進行修改。
2.CopyOnWriteArrayList內部用于存儲元素的Object數組使用volatile
//CopyOnWriteArrayList
private transient volatile Object[] array;
//ArrayList
private transient Object[] elementData;//transient
可以看到區別主要在于CopyOnWriteArrayList的Object是使用volatile來修飾的,volatile可以使變量具備內存可見性,一個線程在工作內存中對變量進行修改后,會立即更新到物理內存,并且使得其他線程中的這個變量緩存失效,其他線程在讀取會去物理內存中讀取最新的值。(volatile修飾的是指向數組的引用變量,所以對數組添加元素,刪除元素不會改變引用,所以為了保證內存可見性,CopyOnWriteArrayList.add()方法在添加元素時,都是復制出一個新數組,進行修改操作后,再設置到就數組上)
注意事項:Object數組都使用transient修飾是因為transient修飾的屬性不會參與序列化,ArrayList通過實現writeObject()和readObject()方法來自定義了序列化方法(基于反序列化時節約空間考慮,如果用默認的序列方法,源elementData數組長度為100,實際只有10個元素,反序列化時也會分配長度為100的數組,造成內存浪費。)
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//1. 使用Lock,保證寫線程在同一時刻只有一個
lock.lock();
try {
//2. 獲取舊數組引用
Object[] elements = getArray();
int len = elements.length;
//3. 創建新的數組,并將舊數組的數據復制到新數組中
Object[] newElements = Arrays.copyOf(elements, len + 1);
//4. 往新數組中添加新的數據
newElements[len] = e;
//5. 將舊數組引用指向新的數組
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
3.SynchronizedList和CopyOnWriteArrayList優缺點
SynchronizedList是通過對讀寫方法使用synchronized修飾來實現同步的,即便只是多個線程在讀數據,也不能進行,如果是讀比較多的場景下,會性能不高,所以適合讀寫均勻的情況。
而CopyOnWriteArrayList是讀寫分離的,只對寫操作加鎖,但是每次寫操作(添加和刪除元素等)時都會復制出一個新數組,完成修改后,然后將新數組設置到舊數組的引用上,所以在寫比較多的情況下,會有很大的性能開銷,所以適合讀比較多的應用場景。
原創文章,作者:9IM,如若轉載,請注明出處:https://www.9im.cn/500.html