日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java Review - 并发编程_并发List_CopyOnWriteArrayList源码剖析

發布時間:2025/3/21 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java Review - 并发编程_并发List_CopyOnWriteArrayList源码剖析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 概述
  • 源碼解析
    • 初始化
    • 添加元素
    • 獲取指定位置元素
    • 修改指定元素
    • 刪除元素
  • 弱一致性的迭代器
    • CopyOnWriteArrayList 是如何實現弱一致性的
  • 小結


概述

并發包中的并發List只有CopyOnWriteArrayList。CopyOnWriteArrayList是一個線程安全的ArrayList,對其進行的修改操作都是在底層的一個復制的數組(快照)上進行的,也就是使用了寫時復制策略。

在CopyOnWriteArrayList的類中,每個CopyOnWriteArrayList對象里面有一個array數組對象用來存放具體元素,ReentrantLock獨占鎖對象用來保證同時只有一個線程對array進行修改。

我們思考幾個問題

  • 何時初始化list,初始化的list元素個數為多少,list是有限大小嗎?

  • 如何保證線程安全,比如多個線程進行讀寫時如何保證是線程安全的?

  • 如何保證使用迭代器遍歷list時的數據一致性?


源碼解析

初始化

首先看下無參構造函數,如下代碼在內部創建了一個大小為0的Object數組作為array的初始值。

/*** Creates an empty list.*/public CopyOnWriteArrayList() {setArray(new Object[0]);}

然后看下有參構造函數。

/*** Creates a list containing the elements of the specified* collection, in the order they are returned by the collection's* iterator.** @param c the collection of initially held elements* @throws NullPointerException if the specified collection is null*/// 入參為集合,將集合中的元素復制到本Listpublic 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);}/*** Creates a list holding a copy of the given array.** @param toCopyIn the array (a copy of this array is used as the* internal array)* @throws NullPointerException if the specified array is null*/// 創建一個List , 其內部元素是toCopyIn的副本public CopyOnWriteArrayList(E[] toCopyIn) {setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));}

添加元素


用來添加元素的函數有 add(E e)、add(int index, E element)、addIfAbsent(E e)和addAllAbsent(Collection<? extends E> c)等 原理類似, 以add(E e)為例

/*** Appends the specified element to the end of this list.** @param e element to be appended to this list* @return {@code true} (as specified by {@link Collection#add})*/public boolean add(E e) {// 1 獲取獨占鎖final ReentrantLock lock = this.lock;lock.lock();try {// 2 獲取arrayObject[] elements = getArray();int len = elements.length;// 3 復制array到新數組,添加元素到新數組Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;// 4 使用新數組代替添加前的數組 setArray(newElements);return true;} finally {// 5 釋放獨占鎖 lock.unlock();}}
  • 首先執行代碼(1)去獲取獨占鎖,如果多個線程都調用add方法則只有一個線程會獲取到該鎖,其他線程會被阻塞掛起直到鎖被釋放。 所以一個線程獲取到鎖后,就保證了在該線程添加元素的過程中其他線程不會對array進行修改
  • 線程獲取鎖后執行代碼(2)獲取array
  • 執行代碼(3)復制array到一個新數組(從這里可以知道新數組的大小是原來數組大小增加1,所以CopyOnWriteArrayList是無界list),并把新增的元素添加到新數組
  • 執行代碼(4)使用新數組替換原數組
  • 執行代碼(5)返回前釋放鎖。

由于加了鎖,所以整個add過程是個原子性操作。需要注意的是,在添加元素時,首先復制了一個快照,然后在快照上進行添加,而不是直接在原來數組上進行。


獲取指定位置元素

使用E get(int index) 獲取下標為index的元素,如果元素不存在則拋出IndexOutOfBoundsException異常。

@SuppressWarnings("unchecked")private E get(Object[] a, int index) {return (E) a[index];}/*** {@inheritDoc}** @throws IndexOutOfBoundsException {@inheritDoc}*/public E get(int index) {return get(getArray(), index);}

在如上代碼中,當線程x調用get方法獲取指定位置的元素時,分兩步走,首先獲取array數組(這里命名為步驟A),然后通過下標訪問指定位置的元素(這里命名為步驟B),這是兩步操作,但是在整個過程中并沒有進行加鎖同步。

由于執行步驟A和步驟B沒有加鎖,這就可能導致在線程x執行完步驟A后執行步驟B前,另外一個線程y進行了remove操作,假設要刪除元素1。

remove操作首先會獲取獨占鎖,然后進行寫時復制操作,也就是復制一份當前array數組,然后在復制的數組里面刪除線程x通過get方法要訪問的元素1,之后讓array指向復制的數組。

而這時候array之前指向的數組的引用計數為1而不是0,因為線程x還在使用它,這時線程x開始執行步驟B,步驟B操作的數組是線程y刪除元素之前的數組,如下圖所示。


所以,雖然線程y已經刪除了index處的元素,但是線程x的步驟B還是會返回index處的元素,這其實就是寫時復制策略產生的弱一致性問題


修改指定元素

使用E set(int index,E element)修改list中指定元素的值,如果指定位置的元素不存在則拋出IndexOutOfBoundsException異常

/*** Replaces the element at the specified position in this list with the* specified element.** @throws IndexOutOfBoundsException {@inheritDoc}*/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 semanticssetArray(elements);}return oldValue;} finally {lock.unlock();}}

首先獲取了獨占鎖,從而阻止其他線程對array數組進行修改,然后獲取當前數組,并調用get方法獲取指定位置的元素,如果指定位置的元素值與新值不一致則創建新數組并復制元素,然后在新數組上修改指定位置的元素值并設置新數組到array。

如果指定位置的元素值與新值一樣,則為了保證volatile語義,還是需要重新設置array,雖然array的內容并沒有改變。


刪除元素

刪除list里面指定的元素,可以使用 E remove(int index) 、 boolean remove(Object o)和boolean remove(Object o, Object[] snapshot, int index) 等方法,它們的原理一樣。

/*** Removes the element at the specified position in this list.* Shifts any subsequent elements to the left (subtracts one from their* indices). Returns the element that was removed from the list.** @throws IndexOutOfBoundsException {@inheritDoc}*/public E remove(int index) {// 獲取獨占鎖 final ReentrantLock lock = this.lock;lock.lock();try {// 獲取數組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 {Object[] newElements = new Object[len - 1];// 分兩次復制刪除后剩余的元素到新數組 System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);// 使用新數組代替舊數組 setArray(newElements);}return oldValue;} finally {// 解鎖 釋放獨占鎖 lock.unlock();}}

其實和新增元素的代碼類似,首先獲取獨占鎖以保證刪除數據期間其他線程不能對array進行修改,然后獲取數組中要被刪除的元素,并把剩余的元素復制到新數組,之后使用新數組替換原來的數組,最后在返回前釋放鎖。


弱一致性的迭代器

遍歷列表元素可以使用迭代器。在講解什么是迭代器的弱一致性前,先舉一個例子來說明如何使用迭代器。

import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/12/1 21:07* @mark: show me the code , change the world*/ public class COWArrayListTest {public static void main(String[] args) {CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList<>();copyOnWriteArrayList.add("hello");copyOnWriteArrayList.add("artisan");copyOnWriteArrayList.add("learn");copyOnWriteArrayList.add("ml");Iterator iterator = copyOnWriteArrayList.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}} }


迭代器的hasNext方法用于判斷列表中是否還有元素,next方法則具體返回元素。

下面來看CopyOnWriteArrayList中迭代器的弱一致性是怎么回事,所謂弱一致性是指返回迭代器后,其他線程對list的增刪改對迭代器是不可見的.

下面看個例子

import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/12/1 21:17* @mark: show me the code , change the world*/ public class COWArrayListTest2 {private static volatile CopyOnWriteArrayList cow = new CopyOnWriteArrayList<>();public static void main(String[] args) throws InterruptedException {cow.add("hello");cow.add("artisan");cow.add("learn");cow.add("ml");Thread thread = new Thread(() -> {System.out.println("異步線程開始操作COW~");// 異步線程修改 刪除cow.set(0, "xxxx");cow.remove(1);cow.remove(2);Iterator iterator = cow.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}System.out.println("異步線程結束操作COW~");});// 一定要在異步線程啟動前獲取到 迭代器Iterator iterator = cow.iterator();// 啟動線程thread.start();// 等待子線程結束thread.join();// 遍歷while (iterator.hasNext()) {System.out.println(iterator.next());}} }


在如上代碼中,main函數首先初始化了cow,然后在啟動線程前獲取到了cow迭代器。子線程thread啟動后首先修改了cow的第1個元素的值,然后刪除了cow中下標為1和2的元素。

主線程在子線程執行完畢后使用獲取的迭代器遍歷數組元素,從輸出結果我們知道,在子線程里面進行的操作一個都沒有生效,這就是迭代器弱一致性的體現。需要注意的是,獲取迭代器的操作必須在子線程操作之前進行。


CopyOnWriteArrayList 是如何實現弱一致性的

/*** Returns an iterator over the elements in this list in proper sequence.** <p>The returned iterator provides a snapshot of the state of the list* when the iterator was constructed. No synchronization is needed while* traversing the iterator. The iterator does <em>NOT</em> support the* {@code remove} method.** @return an iterator over the elements in this list in proper sequence*/public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0);} static final class COWIterator<E> implements ListIterator<E> {/** Snapshot of the array */// array的快照版本private final Object[] snapshot;/** Index of element to be returned by subsequent call to next. */// 數組下標private int cursor;// 構造函數private COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;snapshot = elements;}// 是否遍歷結束public boolean hasNext() {return cursor < snapshot.length;}// 獲取元素@SuppressWarnings("unchecked")public E next() {if (! hasNext())throw new NoSuchElementException();return (E) snapshot[cursor++];}.................................................}

在如上代碼中,當調用iterator()方法獲取迭代器時實際上會返回一個COWIterator對象,COWIterator對象的snapshot變量保存了當前list的內容,cursor是遍歷list時數據的下標。

為什么說snapshot是list的快照呢?明明是指針傳遞的引用啊,而不是副本。如果在該線程使用返回的迭代器遍歷元素的過程中,其他線程沒有對list進行增刪改,那么snapshot本身就是list的array,因為它們是引用關系。但是如果在遍歷期間其他線程對該list進行了增刪改,那么snapshot就是快照了,因為增刪改后list里面的數組被新數組替換了,這時候老數組被snapshot引用。這也說明獲取迭代器后,使用該迭代器元素時,其他線程對該list進行的增刪改不可見,因為它們操作的是兩個不同的數組,這就是弱一致性。

小結

CopyOnWriteArrayList使用寫時復制的策略來保證list的一致性,而獲取—修改—寫入三步操作并不是原子性的,所以在增刪改的過程中都使用了獨占鎖,來保證在某個時間只有一個線程能對list數組進行修改。

另外CopyOnWriteArrayList提供了弱一致性的迭代器,從而保證在獲取迭代器后,其他線程對list的修改是不可見的,迭代器遍歷的數組是一個快照。

另外,CopyOnWriteArraySet的底層就是使用CopyOnWriteArrayList實現的 。

總結

以上是生活随笔為你收集整理的Java Review - 并发编程_并发List_CopyOnWriteArrayList源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。