CopyOnWriteArrayList与Collections.synchronizedList的性能对比

 
列表实现有ArrayList中,矢量,的CopyOnWriteArrayList,Collections.synchronizedList(名单)四种方式。

1 ArrayList

        ArrayList是非线性安全,此类的迭代器和listIterator方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的删除或添加方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出ConcurrentModificationException。即在一方在便利列表,而另一方在修改列表时,会报ConcurrentModificationException错误。而这不是唯一的并发时容易发生的错误,在多线程进行插入操作时,由于没有进行同步操作,容易丢失数据。

 

[java]  查看纯文本在代码上查看代码片派生到我的代码片
 
  1. public boolean  add(E e){   
  2. ensureCapacity(size +  1 );  // Increment modCount !!  
  3. elementData [size ++] = e; //使用了大小++操作,会产生多线程数据丢失问题。  
  4. 返回真;   
  5.    }  
        因此,在开发过程当中,ArrayList中并不适用于多线程的操作。

 

2矢量

        从JDK1.0开始,矢量便存在JDK中,向量是一个线程安全的列表,采用数组实现。其线程安全的实现方式是对所有操作都加上了同步关键字,这种方式严重影响效率,因此,不再推荐使用Vector了,Stackoverflow当中有这样的描述:为什么Java Vector类被认为是过时的或不推荐的?

3 Collections.synchronizedList&CopyOnWriteArrayList

       的CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中的CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比的Cop​​yOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了同步关键字的方式,其读操作性能并不如的CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。

3.1 Collections.synchronizedList

        Collections.synchronizedList的源码可知,其实现线程安全的方式是建立了名单的包装类,代码如下:
[java]  查看纯文本在代码上查看代码片派生到我的代码片
 
 
  1. public static  <T> List <T> synchronizedList(List <T> list){   
  2. 返回 (名单  的instanceof  RandomAccess的?  
  3.                新的 SynchronizedRandomAccessList <T>(列表):  
  4.                新的 SynchronizedList <T>(list)); //根据不同的列表类型最终实现不同的包装类。  
  5.    }  
。其中,SynchronizedList对部分操作加上了同步关键字以保证线程安全但其迭代器()操作还不是线程安全的部分SynchronizedList的代码如下:
[java]  查看纯文本在代码上查看代码片派生到我的代码片
 
 
  1. public  E get(int  index){  
  2.         synchronized (mutex){ return  list.get(index);}  
  3.         }  
  4.     public  E set(int  index,E element){  
  5.         synchronized (mutex){ return  list.set(index,element);}  
  6.         }  
  7.     public void  add(int  index,E element){   
  8.         synchronized (mutex){list.add(index,element);}  
  9.         }  
  10.     public  ListIterator <E> listIterator(){  
  11.         return  list.listIterator(); //必须由用户手动同步需要用户保证同步,否则仍然可能抛出ConcurrentModificationException  
  12.         }  
  13.   
  14.     public  ListIterator <E> listIterator(int  index){  
  15.         return  list.listIterator(index); //必须由用户手动同步<span style =“font-family:Arial,Helvetica,sans-serif;”>需要用户保证同步,否则仍然可能抛出ConcurrentModificationException </ span>  
  16.         }  

3.2 CopyOnWriteArrayList

        从字面可以知道,的CopyOnWriteArrayList在线程对其进行些操作的时候,会拷贝一个新的数组以存放新的字段其写操作的代码如下:
[java]  查看纯文本在代码上查看代码片派生到我的代码片
 
 
  1. / **锁定保护所有mutators * /  
  2.     瞬态最终 的ReentrantLock锁=  新 的ReentrantLock();   
  3.   
  4.     / **数组,只能通过getArray / setArray访问。* /  
  5.     private volatile transient  []数组; //保证了线程的可见性    
  6.       
  7.      public boolean  add(E e){   
  8.     final reentrantLock  lock =  this .lock; // ReentrantLock保证了线程的可见性和顺序性,即保证了多线程安全。  
  9.     lock.lock();  
  10.     尝试 {  
  11.         Object [] elements = getArray();  
  12.         int  len = elements.length;  
  13.         Object [] newElements = Arrays.copyOf(elements,len +  1 ); //在原先数组基础之上新建长度+1的数组,并将原先数组当中的内容拷贝到新数组当中。  
  14.         newElements [len] = e; //设值  
  15.         setArray(newElements); //对新数组进行赋值  
  16.         返回真;   
  17.     }  finally  {  
  18.         lock.unlock();  
  19.     }  
  20.     }  
        其读操作代码如下:
[java]  查看纯文本在代码上查看代码片派生到我的代码片
 
 
  1. public  E get(int  index){  
  2.        return  (E)(getArray()[index]);  
  3.    }  
        其没有加任何同步关键字,根据以上写操作的代码可知,其每次写操作都会进行一次数组复制操作,然后对新复制的数组进行些操作,不可能存在在同时又读写操作在同一个数组上(不是同一个对象),而读操作并没有对数组修改,不会产生线程安全问题的.java中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。
        其中setArray()操作仅仅是对阵列进行引用赋值的.java中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException的,他们是在虚拟机层面阻塞的,而且速度非常快,是一个原子操作,几乎不需要CPU时间。
 
        在列表有更新时直接将原有的列表复制一份,并再新的列表上进行更新操作,完成后再将引用移到新的列表上。旧列表如果仍在使用中(比如遍历)则继续有效。如此一来就不会出现修改了正在使用的对象的情况(读和写分别发生在两个对象上),同时读操作也不必等待写操作的完成,免去了锁的使用加快了读取速度。

3.3 Collections.synchronizedList&CopyOnWriteArrayList在读写操作上的差距

        测试代码:
[java]  查看纯文本在代码上查看代码片派生到我的代码片
 
 
  1. 包 com.yang.test;  
  2.   
  3. import  org.junit.Test;  
  4.   
  5. import  java.util。*;  
  6. import  java.util.concurrent。*;  
  7.   
  8. / ** 
  9.  *创建IntelliJ IDEA。 
  10.  *用户:yangzl2008 
  11.  *日期:14-9-18 
  12.  *时间:下午8:36 
  13.  *要更改此模板,请使用File | 设置| 文件模板 
  14.  * /  
  15. 公共课 Test02 {   
  16.   
  17.     private int  NUM =  10000 ;   
  18.     private int  THREAD_COUNT =  16 ;   
  19.   
  20.     @测试  
  21.     public void  testAdd()  throws  Exception {   
  22.         list <Integer> list1 =  new  CopyOnWriteArrayList <Integer>();  
  23.         list <Integer> list2 = Collections.synchronizedList(new  ArrayList <Integer>());  
  24.         矢量<整数> v =  new  Vector <Integer>();  
  25.   
  26.         CountDownLatch add_countDownLatch =  new  CountDownLatch(THREAD_COUNT);  
  27.         ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);  
  28.   
  29.         int  add_copyCostTime =  0 ;  
  30.         int  add_synchCostTime =  0 ;  
  31.         for  (int  i =  0 ; i <THREAD_COUNT; i ++){  
  32.             add_copyCostTime + = executor.submit(new  AddTestTask(list1,add_countDownLatch))。get();  
  33.         }  
  34.         System.out.println(“CopyOnWriteArrayList add method cost time is”  + add_copyCostTime);  
  35.   
  36.         for  (int  i =  0 ; i <THREAD_COUNT; i ++){  
  37.             add_synchCostTime + = executor.submit(new  AddTestTask(list2,add_countDownLatch))。get();  
  38.         }  
  39.         System.out.println(“Collections.synchronizedList add method cost time is”  + add_synchCostTime);  
  40.   
  41.   
  42.     }  
  43.   
  44.     @测试  
  45.     public void  testGet()  throws  Exception {   
  46.         List <Integer> list = initList();  
  47.   
  48.         list <Integer> list1 =  new  CopyOnWriteArrayList <Integer>(list);  
  49.         list <Integer> list2 = Collections.synchronizedList(list);  
  50.   
  51.         int  get_copyCostTime =  0 ;  
  52.         int  get_synchCostTime =  0 ;  
  53.         ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);  
  54.         CountDownLatch get_countDownLatch =  new  CountDownLatch(THREAD_COUNT);  
  55.         for  (int  i =  0 ; i <THREAD_COUNT; i ++){  
  56.             get_copyCostTime + = executor.submit(new  GetTestTask(list1,get_countDownLatch))。get();  
  57.         }  
  58.         System.out.println(“CopyOnWriteArrayList add method cost time is”  + get_copyCostTime);  
  59.   
  60.         for  (int  i =  0 ; i <THREAD_COUNT; i ++){  
  61.             get_synchCostTime + = executor.submit(new  GetTestTask(list2,get_countDownLatch))。get();  
  62.         }  
  63.         System.out.println(“Collections.synchronizedList add method cost time is”  + get_synchCostTime);  
  64.   
  65.     }  
  66.   
  67.   
  68.     private  List <Integer> initList(){  
  69.         List <Integer> list =  new  ArrayList <Integer>();  
  70.         int  num =  new  Random()。nextInt(1000 );  
  71.         for  (int  i =  0 ; i <NUM; i ++){  
  72.             list.add(NUM);  
  73.         }  
  74.         退货 单  
  75.     }  
  76.   
  77.      AddTestTask  实现 Callable <Integer> {  
  78.         列表<整数>列表;  
  79.         CountDownLatch countDownLatch;  
  80.   
  81.         AddTestTask(List <Integer> list,CountDownLatch countDownLatch){  
  82.             这个.list = list;  
  83.             这个.countDownLatch = countDownLatch;  
  84.         }  
  85.   
  86.         @覆盖  
  87.         public  Integer call()  throws  Exception {  
  88.             int  num =  new  Random()。nextInt(1000 );  
  89.             long  start = System.currentTimeMillis();  
  90.             for  (int  i =  0 ; i <NUM; i ++){  
  91.                 list.add(NUM);  
  92.             }  
  93.             long  end = System.currentTimeMillis();  
  94.             countDownLatch.countDown();  
  95.             return  (int )(end-start);  
  96.         }  
  97.     }  
  98.   
  99.      GetTestTask  实现 Callable <Integer> {  
  100.         列表<整数>列表;  
  101.         CountDownLatch countDownLatch;  
  102.   
  103.         GetTestTask(List <Integer> list,CountDownLatch countDownLatch){  
  104.             这个.list = list;  
  105.             这个.countDownLatch = countDownLatch;  
  106.         }  
  107.   
  108.         @覆盖  
  109.         public  Integer call()  throws  Exception {  
  110.             int  pos =  new  Random()。nextInt(NUM);  
  111.             long  start = System.currentTimeMillis();  
  112.             for  (int  i =  0 ; i <NUM; i ++){  
  113.                 list.get(POS);  
  114.             }  
  115.             long  end = System.currentTimeMillis();  
  116.             countDownLatch.countDown();  
  117.             return  (int )(end-start);  
  118.         }  
  119.     }  
  120. }  
操作结果:
  写操作 读操作
  的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
        写操作:在线程数目增加时的CopyOnWriteArrayList的写操作性能下降非常严重,而Collections.synchronizedList虽然有性能的降低,但下降并不明显。
        读操作:在多线程进行读时,Collections.synchronizedList和的CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加显着。

4结论

        的CopyOnWriteArrayList,发生修改时候做副本,新老版本分离,保证读的高性能,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。而Collections.synchronizedList则可以用在的CopyOnWriteArrayList不适用,但是有需要同步列表的地方,读写操作都比较均匀的地方。
posted @ 2017-08-01 23:34  有你晴天  阅读(887)  评论(0)    收藏  举报