CopyOnWriteArrayList与Collections.synchronizedList的性能对比
列表实现有ArrayList中,矢量,的CopyOnWriteArrayList,Collections.synchronizedList(名单)四种方式。
1 ArrayList
ArrayList是非线性安全,此类的迭代器和listIterator方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的删除或添加方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出ConcurrentModificationException。即在一方在便利列表,而另一方在修改列表时,会报ConcurrentModificationException错误。而这不是唯一的并发时容易发生的错误,在多线程进行插入操作时,由于没有进行同步操作,容易丢失数据。
- public boolean add(E e){
- ensureCapacity(size + 1 ); // Increment modCount !!
- elementData [size ++] = e; //使用了大小++操作,会产生多线程数据丢失问题。
- 返回真;
- }
2矢量
从JDK1.0开始,矢量便存在JDK中,向量是一个线程安全的列表,采用数组实现。其线程安全的实现方式是对所有操作都加上了同步关键字,这种方式严重影响效率,因此,不再推荐使用Vector了,Stackoverflow当中有这样的描述:为什么Java Vector类被认为是过时的或不推荐的?。
3 Collections.synchronizedList&CopyOnWriteArrayList
的CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中的CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比的CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了同步关键字的方式,其读操作性能并不如的CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。
3.1 Collections.synchronizedList
Collections.synchronizedList的源码可知,其实现线程安全的方式是建立了名单的包装类,代码如下:
。其中,SynchronizedList对部分操作加上了同步关键字以保证线程安全但其迭代器()操作还不是线程安全的部分SynchronizedList的代码如下:
- public static <T> List <T> synchronizedList(List <T> list){
- 返回 (名单 的instanceof RandomAccess的?
- 新的 SynchronizedRandomAccessList <T>(列表):
- 新的 SynchronizedList <T>(list)); //根据不同的列表类型最终实现不同的包装类。
- }
- 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 <E> listIterator(){
- return list.listIterator(); //必须由用户手动同步需要用户保证同步,否则仍然可能抛出ConcurrentModificationException
- }
- public ListIterator <E> listIterator(int index){
- return list.listIterator(index); //必须由用户手动同步<span style =“font-family:Arial,Helvetica,sans-serif;”>需要用户保证同步,否则仍然可能抛出ConcurrentModificationException </ span>
- }
从字面可以知道,的CopyOnWriteArrayList在线程对其进行些操作的时候,会拷贝一个新的数组以存放新的字段其写操作的代码如下:
- / **锁定保护所有mutators * /
- 瞬态最终 的ReentrantLock锁= 新 的ReentrantLock();
- / **数组,只能通过getArray / setArray访问。* /
- private volatile transient []数组; //保证了线程的可见性
- public boolean add(E e){
- final reentrantLock lock = this .lock; // ReentrantLock保证了线程的可见性和顺序性,即保证了多线程安全。
- lock.lock();
- 尝试 {
- Object [] elements = getArray();
- int len = elements.length;
- Object [] newElements = Arrays.copyOf(elements,len + 1 ); //在原先数组基础之上新建长度+1的数组,并将原先数组当中的内容拷贝到新数组当中。
- newElements [len] = e; //设值
- setArray(newElements); //对新数组进行赋值
- 返回真;
- } finally {
- lock.unlock();
- }
- }
- public E get(int index){
- return (E)(getArray()[index]);
- }
其中setArray()操作仅仅是对阵列进行引用赋值的.java中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException的,他们是在虚拟机层面阻塞的,而且速度非常快,是一个原子操作,几乎不需要CPU时间。
在列表有更新时直接将原有的列表复制一份,并再新的列表上进行更新操作,完成后再将引用移到新的列表上。旧列表如果仍在使用中(比如遍历)则继续有效。如此一来就不会出现修改了正在使用的对象的情况(读和写分别发生在两个对象上),同时读操作也不必等待写操作的完成,免去了锁的使用加快了读取速度。
测试代码:
- 包 com.yang.test;
- import org.junit.Test;
- import java.util。*;
- import java.util.concurrent。*;
- / **
- *创建IntelliJ IDEA。
- *用户:yangzl2008
- *日期:14-9-18
- *时间:下午8:36
- *要更改此模板,请使用File | 设置| 文件模板
- * /
- 公共课 Test02 {
- private int NUM = 10000 ;
- private int THREAD_COUNT = 16 ;
- @测试
- public void testAdd() throws Exception {
- list <Integer> list1 = new CopyOnWriteArrayList <Integer>();
- list <Integer> list2 = Collections.synchronizedList(new ArrayList <Integer>());
- 矢量<整数> v = new Vector <Integer>();
- 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 <THREAD_COUNT; 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 <THREAD_COUNT; i ++){
- add_synchCostTime + = executor.submit(new AddTestTask(list2,add_countDownLatch))。get();
- }
- System.out.println(“Collections.synchronizedList add method cost time is” + add_synchCostTime);
- }
- @测试
- public void testGet() throws Exception {
- List <Integer> list = initList();
- list <Integer> list1 = new CopyOnWriteArrayList <Integer>(list);
- list <Integer> 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 <THREAD_COUNT; 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 <THREAD_COUNT; 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 <Integer> initList(){
- List <Integer> list = new ArrayList <Integer>();
- int num = new Random()。nextInt(1000 );
- for (int i = 0 ; i <NUM; i ++){
- list.add(NUM);
- }
- 退货 单
- }
- AddTestTask 类实现 Callable <Integer> {
- 列表<整数>列表;
- CountDownLatch countDownLatch;
- AddTestTask(List <Integer> list,CountDownLatch countDownLatch){
- 这个.list = list;
- 这个.countDownLatch = countDownLatch;
- }
- @覆盖
- public Integer call() throws Exception {
- int num = new Random()。nextInt(1000 );
- long start = System.currentTimeMillis();
- for (int i = 0 ; i <NUM; i ++){
- list.add(NUM);
- }
- long end = System.currentTimeMillis();
- countDownLatch.countDown();
- return (int )(end-start);
- }
- }
- GetTestTask 类实现 Callable <Integer> {
- 列表<整数>列表;
- CountDownLatch countDownLatch;
- GetTestTask(List <Integer> list,CountDownLatch countDownLatch){
- 这个.list = list;
- 这个.countDownLatch = countDownLatch;
- }
- @覆盖
- public Integer call() throws Exception {
- int pos = new Random()。nextInt(NUM);
- long start = System.currentTimeMillis();
- for (int i = 0 ; i <NUM; i ++){
- list.get(POS);
- }
- long end = System.currentTimeMillis();
- countDownLatch.countDown();
- return (int )(end-start);
- }
- }
- }
| 写操作 | 读操作 | |||
| 的CopyOnWriteArrayList | 集合。 synchronizedList |
的CopyOnWriteArrayList | 集合。 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 |
读操作:在多线程进行读时,Collections.synchronizedList和的CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加显着。
4结论
的CopyOnWriteArrayList,发生修改时候做副本,新老版本分离,保证读的高性能,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。而Collections.synchronizedList则可以用在的CopyOnWriteArrayList不适用,但是有需要同步列表的地方,读写操作都比较均匀的地方。

浙公网安备 33010602011771号