前言 今天我們一起學習下java.util.concurrent併發包里的 "CopyOnWriteArrayList" 工具類。當有多個線程可能同時遍歷、修改某個公共數組時候,如果不希望因使用 synchronize 關鍵字鎖住整個數組而影響性能,可以考慮使用CopyOnWriteArrayLis ...
前言
今天我們一起學習下java.util.concurrent併發包里的CopyOnWriteArrayList工具類。當有多個線程可能同時遍歷、修改某個公共數組時候,如果不希望因使用synchronize關鍵字鎖住整個數組而影響性能,可以考慮使用CopyOnWriteArrayList。
CopyOnWriteArrayList API
CopyOnWriteArrayList的定義如下:
public class CopyOnWriteArrayList<E>
extends Object
implements List<E>, RandomAccess, Cloneable, Serializable
它也屬於Java集合框架的一部分,是ArrayList的線程安全的變體,跟ArrayList的不同在於:CopyOnWriteArrayList針對數組的修改操作(add、set等)是基於內部拷貝的一份數據而進行的。換句話說,即使在一個線程進行遍歷操作時有其他線程可能進行插入或刪除操作,我們也可以“線程安全”得遍歷CopyOnWriteArrayList。
例子1:插入(刪除)數據的同時進行遍歷
CopyOnWriteArrayList的實現原理是,在一個線程開始遍歷(創建Iterator對象)時,內部會創建一個“快照”數組,遍歷基於這個快照Iterator進行,在遍歷過程中這個快照數組不會改變,也就不會拋出ConcurrentModificationException
。如果在遍歷的過程中有其他線程嘗試改變數組的內容,就會拷貝一份新的數據進行變更,而後面再來訪問這個數組的線程,看到的就是變更過的數組。
創建一個CopyOnWriteArrayList數組numbers;
CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});
創建一個遍歷器iterator;
Iterator<Integer> iterator = numbers.iterator();
給numbers中增加(或刪除、修改)一個元素;
numbers.add(100);
利用iterator遍曆數組的元素,發現遍歷的結果是Iterator對象創建之前的;
List<Integer> result = new LinkedList<>(); iterator.forEachRemaining(result::add); assertThat(result).containsOnly(1, 3, 5, 78);
完整的例子如下:
package org.java.learn.concurrent.copyonwritearraylist;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.assertj.core.api.Assertions.*;
/**
* 作用:
* User: duqi
* Date: 2017/11/9
* Time: 11:20
*/
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});
Iterator<Integer> iterator = numbers.iterator();
numbers.add(100);
List<Integer> result = new LinkedList<>();
iterator.forEachRemaining(result::add);
assertThat(result).containsOnly(1, 3, 5, 78);
Iterator<Integer> iterator2 = numbers.iterator();
numbers.remove(3);
List<Integer> result2 = new LinkedList<>();
iterator2.forEachRemaining(result2::add);
assertThat(result2).containsOnly(1, 3, 5, 78, 100);
}
}
例子2:不支持一邊遍歷一邊刪除
由於CopyOnWriteArrayList的實現機制——>修改操作和讀操作拿到的Iterator對象指向的不是一個數組,因此不支持基於Iterator對象的方法結果的刪除:public void remove();
,例子代碼如下:
package org.java.learn.concurrent.copyonwritearraylist;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 作用: User: duqi Date: 2017/11/9 Time: 13:40
*/
public class CopyOnWriteArrayListExample2 {
public static void main(String[] args) {
try {
testExceptionThrow();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void testExceptionThrow() {
CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 78});
Iterator<Integer> integerIterator = numbers.iterator();
while (integerIterator.hasNext()) {
integerIterator.remove();
}
}
}
結論
CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景里,比如緩存。發生修改時候做copy,新老版本分離,保證讀的高性能,適用於以讀為主的情況。
參考資料
本號專註於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。