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操作时,可能会产生异常
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。


