概述
1 ArrayList
ArrayList是非线性安全,此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。即在一方在便利列表,而另一方在修改列表时,会报ConcurrentModificationException错误。而这不是唯一的并发时容易发生的错误,在多线程进行插入操作时,由于没有进行同步操作,容易丢失数据。
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;//使用了size++操作,会产生多线程数据丢失问题。
return true;
}
因此,在开发过程当中,ArrayList并不适用于多线程的操作。
2 Vector
从JDK1.0开始,Vector便存在JDK中,Vector是一个线程安全的列表,采用数组实现。其线程安全的实现方式是对所有操作都加上了synchronized关键字,这种方式严重影响效率,因此,不再推荐使用Vector了,Stackoverflow当中有这样的描述:Why is Java Vector class considered obsolete or deprecated?。
3 Collections.synchronizedList & CopyOnWriteArrayList
CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。
3.1 Collections.synchronizedList
Collections.synchronizedList的源码可知,其实现线程安全的方式是建立了list的包装类,代码如下:
public static List synchronizedList(List list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList(list) :
new SynchronizedList(list));//根据不同的list类型最终实现不同的包装类。
}
其中,SynchronizedList对部分操作加上了synchronized关键字以保证线程安全。但其iterator()操作还不是线程安全的。部分SynchronizedList的代码如下:
public E get(int index) {
synchronized(mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized(mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized(mutex) {list.add(index, element);}
}
public ListIterator listIterator() {
return list.listIterator(); // Must be manually synched by user 需要用户保证同步,否则仍然可能抛出ConcurrentModificationException
}
public ListIterator listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user 需要用户保证同步,否则仍然可能抛出ConcurrentModificationException
}
3.2 CopyOnWriteArrayList
从字面可以知道,CopyOnWriteArrayList在线程对其进行些操作的时候,会拷贝一个新的数组以存放新的字段。其写操作的代码如下:
/** 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;//ReentrantLock 保证了线程的可见性和顺序性,即保证了多线程安全。
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//在原先数组基础之上新建长度+1的数组,并将原先数组当中的内容拷贝到新数组当中。
newElements[len] = e;//设值
setArray(newElements);//对新数组进行赋值
return true;
} finally {
lock.unlock();
}
}
其读操作代码如下:
public E get(int index) {
return (E)(getArray()[index]);
}
其没有加任何同步关键字,根据以上写操作的代码可知,其每次写操作都会进行一次数组复制操作,然后对新复制的数组进行些操作,不可能存在在同时又读写操作在同一个数组上(不是同一个对象),而读操作并没有对数组修改,不会产生线程安全问题。Java中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。
其中setArray()操作仅仅是对array进行引用赋值。Java中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,是一个原子操作,几乎不需要CPU时间。
在列表有更新时直接将原有的列表复制一份,并再新的列表上进行更新操作,完成后再将引用移到新的列表上。旧列表如果仍在使用中(比如遍历)则继续有效。如此一来就不会出现修改了正在使用的对象的情况(读和写分别发生在两个对象上),同时读操作也不必等待写操作的完成,免去了锁的使用加快了读取速度。
3.3 Collections.synchronizedList & CopyOnWriteArrayList在读写操作上的差距
测试代码:
package com.yang.test;
import org.junit.Test;
import java.util.*;
import java.util.concurrent.*;
/**
* Created with IntelliJ IDEA.
* User: yangzl2008
* Date: 14-9-18
* Time: 下午8:36
* To change this template use File | Settings | File Templates.
*/
public class Test02 {
private int NUM = 10000;
private int THREAD_COUNT = 16;
@Test
public void testAdd() throws Exception {
List list1 = new CopyOnWriteArrayList();
List list2 = Collections.synchronizedList(new ArrayList());
Vector v = new Vector();
CountDownLatch add_countDownLatch = new CountDownLatch(THREAD_COUNT);
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
int add_copyCostTime = 0;
int add_synchCostTime = 0;
for (int i = 0; i
add_copyCostTime += executor.submit(new AddTestTask(list1, add_countDownLatch)).get();
}
System.out.println("CopyOnWriteArrayList add method cost time is " + add_copyCostTime);
for (int i = 0; i
add_synchCostTime += executor.submit(new AddTestTask(list2, add_countDownLatch)).get();
}
System.out.println("Collections.synchronizedList add method cost time is " + add_synchCostTime);
}
@Test
public void testGet() throws Exception {
List list = initList();
List list1 = new CopyOnWriteArrayList(list);
List list2 = Collections.synchronizedList(list);
int get_copyCostTime = 0;
int get_synchCostTime = 0;
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch get_countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i
get_copyCostTime += executor.submit(new GetTestTask(list1, get_countDownLatch)).get();
}
System.out.println("CopyOnWriteArrayList add method cost time is " + get_copyCostTime);
for (int i = 0; i
get_synchCostTime += executor.submit(new GetTestTask(list2, get_countDownLatch)).get();
}
System.out.println("Collections.synchronizedList add method cost time is " + get_synchCostTime);
}
private List initList() {
List list = new ArrayList();
int num = new Random().nextInt(1000);
for (int i = 0; i
list.add(num);
}
return list;
}
class AddTestTask implements Callable {
List list;
CountDownLatch countDownLatch;
AddTestTask(List list, CountDownLatch countDownLatch) {
this.list = list;
this.countDownLatch = countDownLatch;
}
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(1000);
long start = System.currentTimeMillis();
for (int i = 0; i
list.add(num);
}
long end = System.currentTimeMillis();
countDownLatch.countDown();
return (int) (end - start);
}
}
class GetTestTask implements Callable {
List list;
CountDownLatch countDownLatch;
GetTestTask(List list, CountDownLatch countDownLatch) {
this.list = list;
this.countDownLatch = countDownLatch;
}
@Override
public Integer call() throws Exception {
int pos = new Random().nextInt(NUM);
long start = System.currentTimeMillis();
for (int i = 0; i
list.get(pos);
}
long end = System.currentTimeMillis();
countDownLatch.countDown();
return (int) (end - start);
}
}
}
操作结果:
写操作
读操作
CopyOnWriteArrayList
Collections.
synchronizedList
CopyOnWriteArrayList
Collections.
synchronizedList
2
567
2
1
1
4
3088
3
2
2
8
25975
28
2
3
16
295936
44
2
6
32
-
-
3
8
64
-
-
7
21
128
-
-
9
38
写操作:在线程数目增加时CopyOnWriteArrayList的写操作性能下降非常严重,而Collections.synchronizedList虽然有性能的降低,但下降并不明显。
读操作:在多线程进行读时,Collections.synchronizedList和CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加显著。
4 结论
CopyOnWriteArrayList,发生修改时候做copy,新老版本分离,保证读的高性能,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。而Collections.synchronizedList则可以用在CopyOnWriteArrayList不适用,但是有需要同步列表的地方,读写操作都比较均匀的地方。
最后
以上就是忧郁春天为你收集整理的java的list读写性能_CopyOnWriteArrayList与Collections.synchronizedList的性能对比(转)的全部内容,希望文章能够帮你解决java的list读写性能_CopyOnWriteArrayList与Collections.synchronizedList的性能对比(转)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复