标签:test and star 修改 detail 容器 from arrays 回收
今天介绍的主角是CopyOnWriteArrayList类,是jdk1.5才加入的一个并发集合类,它是ArrayList的Thread-safe的变体,属于COW的一种,COW系列的还有CopyOnWriteArraySet集合。COW是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。
先给出结论:
CopyOnWriteArrayList适用于读操作比写操作多很多的并发场景。如果读操作和写操作的频次相差不大时,建议使用Collections.synchornizedList。
先看CopyOnWriteArrayList的类声明
public class CopyOnWriteArrayList<E> extends Object implements List<E>, RandomAccess, Cloneable, Serializable
1. CopyOnWriteArrayList的由来
CopyOnWriteArrayList是在Jdk1.5的concurrent包中引入的,concurrent包的类都是为了高效并发才引入的。Jdk1.5以前,针对并发场景,能使用List的方式只能通过Collections.synchornizedList方式产生或是自己使用synchronized关键字(Vector类,因效率过低已被废弃)来实现。我们知道容器在多线程读与读之间是并不存在资源竞争的。所以直接使用synchornized实现,在某些场景下,并不高效。由此就产生了CopyOnWriteArrayList。
2. CopyOnWriteArrayList与Collections.synchornizedList的性能比较
上代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
/**
* Created by Administrator on 2017/9/4.
*/
public class ListDemo {
private static final int SIZE = 10000;
public static long testAddList(List<Integer> list){
long startTime = System.currentTimeMillis();
for(int i = 0; i <SIZE; i++){
list.add(i);
}
long time= System.currentTimeMillis() - startTime;
return time;
}
public static long testGetList(List<Integer> list){
long start = System.currentTimeMillis();
for (int i = 0; i < SIZE; i++) {
list.get(i);
}
long time= System.currentTimeMillis() - start;
return time;
}
public static void main(String [] args){
ArrayList<Integer> list = new ArrayList<>();
List<Integer> list2 = Collections.synchronizedList(list);
CopyOnWriteArrayList<Integer> list3 = new CopyOnWriteArrayList<>();
//多线程测试性能;
long addSynchronizedListTime = 0L, addCopyOnWriteArrayListTime = 0L;
long getSynchronizedListTime = 0L, getCopyOnWriteArrayListTime = 0L;
ExecutorService service = Executors.newCachedThreadPool();
//测试synchornizedList的写和读操作的性能;
for(int i = 0 ; i <5; i++) {
try {
addSynchronizedListTime += service.submit(new AddDataRunnable(list2)).get();
getSynchronizedListTime += service.submit(new GetDataRunnable(list2)).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//测试CopyOnWriteArrayList的写和读操作性能;
for(int i = 0 ; i <5; i++) {
try {
addCopyOnWriteArrayListTime += service.submit(new AddDataRunnable(list3)).get();
getCopyOnWriteArrayListTime += service.submit(new GetDataRunnable(list3)).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.println("addSynchornizedTime:"+addSynchronizedListTime);
System.out.println("getSynchornizedTime:"+getSynchronizedListTime);
System.out.println("addCopyOnWriteArrayListTime:"+addCopyOnWriteArrayListTime);
System.out.println("getCopyOnWriteArrayListTime:"+getCopyOnWriteArrayListTime);
}
static class AddDataRunnable implements Callable<Long>{
private List<Integer> mList;
public AddDataRunnable(List<Integer> l){
this.mList = l;
}
@Override
public Long call() throws Exception {
long time = testAddList(mList);
return time;
}
}
static class GetDataRunnable implements Callable<Long>{
private List<Integer> mList;
public GetDataRunnable(List<Integer> l){
this.mList = l;
}
@Override
public Long call() throws Exception {
long time = testGetList(mList);
return time;
}
}
}
运行结果如下:
addSynchornizedTime:3 getSynchornizedTime:3 addCopyOnWriteArrayListTime:1324 getCopyOnWriteArrayListTime:0 Process finished with exit code 0
可以从运行结果中得出结论:
Collections.synchronizedList的整体的读与写性能都比较稳定。而CopyOnWriteArrayList在写方面,表现的非常差,在读操作上,却非常优秀。
所以CopyOnWriteArrayList适合使用在读操作比较多的并发场景。
3. CopyOnWriteArrayList的代码分析
我们接下来分析下为何CopyOnWriteArrayList有此特性,前面已经提到,针对读操作,是不做处理,和普通的ArrayList性能一样。而在写操作(修改时),会先拷贝一份,实现新旧版本的分离,然后在拷贝的版本上进行修改操作,修改完后,将其更新至就版本中。
我们以add方法为例:
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
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; //对新数组执行add操作;
setArray(newElements);//将新数组更新至arrays
return true;
} finally {
lock.unlock(); //释放锁;
}
}
再看一下get方法
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
get方法中没有加锁,就是普普通通的ArrayList的get操作;
这里我们就可以知道CopyOnWriteArrayList面对写操作为什么性能低下了?因为首先需要去lock,有可能需要等待时间去获取锁,还有就是每一步的写操作,都会发生Arrays.copyOf的拷贝操作。
4. ConcurrentModificationException异常
普通的ArrayList在遍历成员时,如果修改集合,则会报出ConcurrentModificationException异常。而CopyOnWriteArrayList的实现,在遍历时,修改并不会报出该异常。
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by Administrator on 2017/9/4.
*/
public class ExceptionDemo {
public static void main(String [] args){
// ArrayList<Integer> list = new ArrayList<>();
// list.add(1);
// list.add(2);
// list.add(3);
//
// for(int a: list){
// list.add(5);
// }
/*
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at ExceptionDemo.main(ExceptionDemo.java:18)
Process finished with exit code 1
*/
CopyOnWriteArrayList<Integer> l = new CopyOnWriteArrayList<>();
l.add(1);
l.add(2);
l.add(3);
for(int a: l){
l.add(5);
}
}
}
5. CopyOnWriteArrayList的缺点
COW思想,是一种实现读写分离的思想,优化了读操作的性能。因为其实现所以存在以下的缺点:
内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,会造成GC的回收,引发性能问题。针对内存紧张的场景,建议使用其他的并发容器代替。
数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
参考链接:
http://www.cnblogs.com/dolphin0520/p/3938914.html
http://blog.csdn.net/zljjava/article/details/48139465
标签:test and star 修改 detail 容器 from arrays 回收
原文地址:http://www.cnblogs.com/yw-technology/p/7476106.html