CopyOnWriteArrayList线程安全分析
CopyOnWriteArraylist简介说明
CopyOnWriteArrayList是java中常用的一种并发容器多用于读多写少的并发场景
但是注意:CopyOnWriteArrayList并不是线程安全
CopyOnWriteArrayList原理
CopyOnWriteArrayList向容器中添加或删除元素时,不直接往当前容器添加删除,而是先将当前容器进行Copy,复制出一个新的容器然后新的容器里添加删除元素,添加删除完元素之后
再将原容器的引用指向新的容器,整个过程加锁,保证了写的线程安全
例:
public boolean add(E e) { synchronized (lock) { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } } public E remove(int index) { synchronized (lock) { 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; } }
上述的思路,写操作不会对当前容器进行任何操作 那么对容器的并发读,则无需加锁,这是一种读写分离的思想
public E get(int index) { return get(getArray(), index); }通常我们会使用一个线程向容器中添加元素
一个线程来读取元素,而读取的操作往往更加频繁
写操作加锁保证了线程安全,读写分离保证了读操作的效率
数组越界注意事项
当有一个线程进行删除元素操作读线程去读取容器中最后一个元素
读之前的时候容器大小为i
当去读的时候删除线程突然删除了一个元素
这个时候容器大小变为了i-1
读线程仍然去读取第i个元素,这时候就会发生数组越界
例:
CopyOnWriteArrayList删除测试 向list中放入10000个测试数据
启动两个线程
一个不断的删除元素
一个不断的读取容器中最后一个数据
public void test(){ for(int i = 0; i<10000; i++){ list.add("string" + i); } new Thread(new Runnable() { @Override public void run() { while (true) { if (list.size() > 0) { String content = list.get(list.size() - 1); }else { break; } } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { if(list.size() <= 0){ break; } list.remove(0); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }
从以上的代码中,我们可以看出并不是线程安全, 当涉及remove操作时,可能会产生异常
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。