前言
今天咱們一塊兒學習下java.util.concurrent併發包裏的CopyOnWriteArrayList工具類。當有多個線程可能同時遍歷、修改某個公共數組時候,若是不但願因使用synchronize關鍵字鎖住整個數組而影響性能,能夠考慮使用CopyOnWriteArrayList。html
CopyOnWriteArrayList API
CopyOnWriteArrayList的定義以下:java
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;api
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();
,例子代碼以下:oracle
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面試題、我的成長和自我管理等主題,爲讀者提供一線開發者的工做和成長經驗,期待你能在這裏有所收穫。
原文出處:https://www.cnblogs.com/javaadu/p/11229161.html