java并發(fā)包中并發(fā)List源碼剖析
介紹
CopyOnWriteArrayList
線程安全的ArrayList,對其進行的修改操作都是在底層的一個復制的數(shù)組(快照)進行的,也就是寫時復制策略
類圖
每一個對象里面有一個array數(shù)組進行存放具體的元素,ReentrantLock獨占鎖對象用來保證同時只有一個線程對array進行修改,這里只要記得ReentrantLock是獨占鎖,同時只有一個線程可以獲取就可以了
主要方法的源碼解析
初始化
無參構造創(chuàng)建一個大小為零的object數(shù)組
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
有參構造
//創(chuàng)建一個list,內部是入?yún)oCopyIn副本
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
//入?yún)榧?將集合的元素復制到笨list
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
if (c.getClass() != ArrayList.class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
添加元素
public boolean add(E e) {
//獲取獨占鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//獲取array
Object[] elements = getArray();
//復制array到新數(shù)組,添加元素到新數(shù)組
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//使用新數(shù)組替換添加前的數(shù)組
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
調用 add 方法的線程會首先執(zhí)行代碼(1)去獲取獨占鎖,如果多個我程都調用 add 方法則只有一個線程會獲取到該鎖,其他線程會被阻塞掛起直到鎖被釋放所以一個線程獲取到鎖后,就保證了在該線程添加元素的過程中其他線程不會對amay進行修改。
線程獲取鎖后執(zhí)行代碼(2)獲取array,然后執(zhí)行代碼(3)復制 array 到一個新數(shù)組(M這里可以知道新數(shù)組的大小是原來數(shù)組大小增加 1,所以 CopyOnWriteArayList 是無界is),并把新增的元素添加到新數(shù)組。然后執(zhí)行代碼(4)使用新數(shù)組替換原數(shù)組,并在返回前釋放鎖。由于加了鎖,所以針咖過程是個原子性操作。需要注意的是,在添加元素時,首先復制了一個快照,然在快照上進行添加,而不是直接在原來數(shù)組上進行。
獲取指定位置元素
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
線程x調用get方法獲取指定位置的元素時候,分兩步,首先獲取array數(shù)組(步驟A),然后通過下標訪問指定位置的元素(步驟B),這兩步操作并沒有進行加鎖同步
沒有加鎖會導致線程x在執(zhí)行完步驟A后執(zhí)行步驟B前,另外一個線程y進行了remove操作,假設當前要刪除的元素1,remove操作首先會獲取獨占鎖,然后進行寫時復制操作,也就是復制一份當前array數(shù)組,然后輔助的數(shù)組里面刪除線程x通過get方法要訪問的元素1,之后讓array指向復制的數(shù)組,而這個時候array之前指向的數(shù)組的引用計數(shù)為1而不是0,因為線程x還在使用它,這時候線程下開始執(zhí)行步驟B,步驟B操作的是線程y刪除元素之前的數(shù)組
修改指定元素
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
首先獲取獨占鎖,從而阻止其他線程對array數(shù)組進行修改,然后獲取當前數(shù)組,并調用get方法獲取指定位置的元素,如果指定位置的元素值與新值不一致則創(chuàng)建新數(shù)組并復制元素,然后在新數(shù)組上修改指定位置的元素值并設置新數(shù)組到array,如果指定位置的元素值與新值一樣,則為了保證volatile語義,還要重新設置array
刪除元素
public E remove(int index) {
//獲取獨占鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//統(tǒng)計數(shù)組
Object[] elements = getArray();
int len = elements.length;
//獲取指定元素
E oldValue = get(elements, index);
int numMoved = len - index - 1;
//如果要刪除的是最后一個元素
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//分兩次復制刪除后剩余元素到新數(shù)組
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
//使用新數(shù)組代替老數(shù)組
setArray(newElements);
}
return oldValue;
} finally {
//釋放鎖
lock.unlock();
}
}
弱一致性的迭代器
public class IteratorTest {
public static void main(String[] args) {
CopyOnWriteArrayList<String> arrayList=new CopyOnWriteArrayList<>();
arrayList.add("hello");
arrayList.add("abbbb");
Iterator<String> iterator=arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
返回迭代器后,其他線程對list的增刪改對迭代器是不可見的
public Spliterator<E> spliterator() {
return Spliterators.spliterator
(getArray(), Spliterator.IMMUTABLE | Spliterator.ORDERED);
}
static final class COWIterator<E> implements ListIterator<E> {
//array的快照版本
private final Object[] snapshot;
//下標
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
//是否遍歷結束
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
@SuppressWarnings("unchecked")
//獲取元素
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
當調用iterator0 方法獲取選代器時實際上會返回一個COWIterator對象,COWIterator 對象的 snapshot變量保存了當前 list 的內容,cursor 是遍歷 list時數(shù)據(jù)下標。
為什么說snapshot 是 list 的快照呢?
明明是指針傳遞的引用啊,而不是副本。在該線程使用返回的迭代器遍歷元素的過程中,其他線程沒有對 list 進行增刪改,那snapshot本身就是 list的array,因為它們是引用關系。但是如果在遍歷期間其他線程對list進行了增刪改,那么 snapshot就是快照了,因為增刪改后 list里面的數(shù)組被新數(shù)組替換這時候老數(shù)組被 snapshot 引用。這也說明獲取迭代器后,使用該迭代器元素時,其他線程對該list進行增刪改不可見,因為他們操作的是兩個不同的數(shù)組文章來源:http://www.zghlxwxcb.cn/news/detail-438177.html
/**
* @author xingchen
* @version V1.0
* @Package com.并發(fā).Test
* @date 2023/4/21 13:42
*
* 學習弱一致性案例
*/
public class CopyListTest {
private static volatile CopyOnWriteArrayList<String> arrayList=new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException{
arrayList.add("hello");
arrayList.add("hello");
arrayList.add("hello");
arrayList.add("hello");
arrayList.add("hello");
arrayList.add("hello");
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
arrayList.set(1,"baba");
arrayList.remove(2);
arrayList.remove(3);
}
});
Iterator<String > iterator=arrayList.iterator();
thread.start();
thread.join();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
總結
CopyOnWriteArrayList使用寫時復制策略來保證list的一致性,而獲取–修改–寫入三步操作并不是原子性的,所以在增刪改的時候都使用了獨占鎖,來保證在某個時候只有一個線程能對list進行修改,另外CopyOnWriteArrayList提供的弱一致性的迭代器,從而保證在獲取迭代器后,其他線程對list的修改是不可見的,迭代器遍歷的數(shù)組是一個快照,CopyOnWriteArrayList底層就是使用它實現(xiàn)的文章來源地址http://www.zghlxwxcb.cn/news/detail-438177.html
到了這里,關于java并發(fā)編程之美第五章讀書筆記的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!