在多線程中,若是A線程在讀取一個List時,B線程在向裏面add數據,會拋出異常:java.util.ConcurrentModificationExceptionjava
public class ErrorTest { public static void main(String[] args) throws InterruptedException { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); final ArrayList<Integer> list_thrd = new ArrayList<>(list); Thread thread = new Thread(new Runnable() { @Override public void run() { int count = 0; while (true) { list_thrd.add(count++); } } }); thread.setDaemon(true); thread.start(); Thread.sleep(3); for (Integer integer : list_thrd) { System.out.println(integer); } } }
運行這段代碼,會拋出java.util.ConcurrentModificationException,即主線程在遍歷list元素的時候,子線程在向裏面添加元素。在這種狀況下,咱們可使用CopyOnWriteArrayList進行操做。多線程
CopyOnWrite,字面意思就是寫時複製。通俗的說,就是咱們向容器中添加數據的時候,並非向容器自己添加數據。而是,先copy一個副本,向裏面添加記錄,而後再將copy賦值給原對象。咱們來看下面的代碼併發
public class CopyOnWriteListTest { public static void main(String[] args) throws InterruptedException { List<Integer> temp = Arrays.asList(1, 2, 3, 4, 5); final CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(temp); Thread thread = new Thread(new Runnable() { @Override public void run() { int count = 0; while (true) { list.add(count++); } } }); thread.setDaemon(true); thread.start(); Thread.sleep(3); for (Integer integer : list) { System.out.println(list.hashCode()); System.out.println(integer); } } }
咱們在遍歷CopyOnWriteArrayList集合的時候,同時打印出hashCode,能夠看到輸出結果:ide
-1813306013 1 1381289098 2 1109838168 3 -1094901371 4 -1008565321 5 -1813169375 0 -1540165649 1 -1441641759 2 1807730922 3 -870434537 4 -1185576945 5
這說明CopyOnWriteArrayList的add方法確實是建立了新的list對象,同時也不會拋出異常。this
/** The lock protecting all mutators */ transient final ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private volatile transient Object[] array; public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
private E get(Object[] a, int index) { return (E) a[index]; }
get方法沒有使用鎖,因此讀取的仍是舊的數據,由於寫入的時候並無鎖住舊的array對象spa
從剛剛的例子中咱們能夠看出,CopyOnWriteArrayList適合讀多寫少的場景。例如黑白名單等,將數據初始化到List當中,當有較少寫操做的時候,能夠保證多併發狀況下,List的最終一致性。可是,它也是有缺點的:當執行add操做的時候,因爲要copy出一個對象,這時會致使內存佔用加大。可能會形成頻繁的Young GC和Full GC。線程