zoukankan      html  css  js  c++  java
  • 使用多线程往LIST添加数据 线程安全list CopyOnWriteArrayList与Collections.synchronizedList的性能对比

    列表实现有ArrayList、Vector、CopyOnWriteArrayList、Collections.synchronizedList(list)四种方式。

    1 ArrayList

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

    [java]  view plain copy 在CODE上查看代码片 派生到我的代码片
     
     
     
    1. public boolean add(E e) {  
    2. ensureCapacity(size + 1);  // Increments modCount!!  
    3. elementData[size++] = e;//使用了size++操作,会产生多线程数据丢失问题。  
    4. return true;  
    5.    }  
            因此,在开发过程当中,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的包装类,代码如下: 
    [java]  view plain copy 在CODE上查看代码片 派生到我的代码片
     
     
     
    1. public static <T> List<T> synchronizedList(List<T> list) {  
    2. return (list instanceof RandomAccess ?  
    3.                new SynchronizedRandomAccessList<T>(list) :  
    4.                new SynchronizedList<T>(list));//根据不同的list类型最终实现不同的包装类。  
    5.    }  
    其中,SynchronizedList对部分操作加上了synchronized关键字以保证线程安全。但其iterator()操作还不是线程安全的。部分SynchronizedList的代码如下:
    [java]  view plain copy 在CODE上查看代码片 派生到我的代码片
     
     
     
    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(); // Must be manually synched by user 需要用户保证同步,否则仍然可能抛出ConcurrentModificationException  
    12.         }  
    13.   
    14.     public ListIterator<E> listIterator(int index) {  
    15.         return list.listIterator(index); // Must be manually synched by user <span style="font-family: Arial, Helvetica, sans-serif;">需要用户保证同步,否则仍然可能抛出ConcurrentModificationException</span>  
    16.         }  

    3.2 CopyOnWriteArrayList

            从字面可以知道,CopyOnWriteArrayList在线程对其进行些操作的时候,会拷贝一个新的数组以存放新的字段。其写操作的代码如下:
    [java]  view plain copy 在CODE上查看代码片 派生到我的代码片
     
     
     
    1. /** The lock protecting all mutators */  
    2.     transient final ReentrantLock lock = new ReentrantLock();  
    3.   
    4.     /** The array, accessed only via getArray/setArray. */  
    5.     private volatile transient Object[] array;//保证了线程的可见性  
    6.       
    7.      public boolean add(E e) {  
    8.     final ReentrantLock lock = this.lock;//ReentrantLock 保证了线程的可见性和顺序性,即保证了多线程安全。  
    9.     lock.lock();  
    10.     try {  
    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.         return true;  
    17.     } finally {  
    18.         lock.unlock();  
    19.     }  
    20.     }  
            其读操作代码如下:
    [java]  view plain copy 在CODE上查看代码片 派生到我的代码片
     
     
     
    1. public E get(int index) {  
    2.        return (E)(getArray()[index]);  
    3.    }  
            其没有加任何同步关键字,根据以上写操作的代码可知,其每次写操作都会进行一次数组复制操作,然后对新复制的数组进行些操作,不可能存在在同时又读写操作在同一个数组上( 不是同一个对象),而读操作并没有对数组修改,不会产生线程安全问题。Java中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。 
            其中setArray()操作仅仅是对array进行引用赋值。Java中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,是一个原子操作,几乎不需要CPU时间。
     
            在列表有更新时直接将原有的列表复制一份,并再新的列表上进行更新操作,完成后再将引用移到新的列表上。旧列表如果仍在使用中(比如遍历)则继续有效。如此一来就不会出现修改了正在使用的对象的情况(读和写分别发生在两个对象上),同时读操作也不必等待写操作的完成,免去了锁的使用加快了读取速度。

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

            测试代码:
    [java]  view plain copy 在CODE上查看代码片 派生到我的代码片
     
     
     
    1. package com.yang.test;  
    2.   
    3. import org.junit.Test;  
    4.   
    5. import java.util.*;  
    6. import java.util.concurrent.*;  
    7.   
    8. /** 
    9.  * Created with IntelliJ IDEA. 
    10.  * User: yangzl2008 
    11.  * Date: 14-9-18 
    12.  * Time: 下午8:36 
    13.  * To change this template use File | Settings | File Templates. 
    14.  */  
    15. public class Test02 {  
    16.   
    17.     private int NUM = 10000;  
    18.     private int THREAD_COUNT = 16;  
    19.   
    20.     @Test  
    21.     public void testAdd() throws Exception {  
    22.         List<Integer> list1 = new CopyOnWriteArrayList<Integer>();  
    23.         List<Integer> list2 = Collections.synchronizedList(new ArrayList<Integer>());  
    24.         Vector<Integer> 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.     @Test  
    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.         return list;  
    75.     }  
    76.   
    77.     class AddTestTask implements Callable<Integer> {  
    78.         List<Integer> list;  
    79.         CountDownLatch countDownLatch;  
    80.   
    81.         AddTestTask(List<Integer> list, CountDownLatch countDownLatch) {  
    82.             this.list = list;  
    83.             this.countDownLatch = countDownLatch;  
    84.         }  
    85.   
    86.         @Override  
    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.     class GetTestTask implements Callable<Integer> {  
    100.         List<Integer> list;  
    101.         CountDownLatch countDownLatch;  
    102.   
    103.         GetTestTask(List<Integer> list, CountDownLatch countDownLatch) {  
    104.             this.list = list;  
    105.             this.countDownLatch = countDownLatch;  
    106.         }  
    107.   
    108.         @Override  
    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  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不适用,但是有需要同步列表的地方, 读写操作都比较均匀的地方。
  • 相关阅读:
    99%的Python用户都不知道的f-string隐秘技巧
    (数据科学学习手札115)Python+Dash快速web应用开发——交互表格篇(上)
    (数据科学学习手札114)Python+Dash快速web应用开发——上传下载篇
    (数据科学学习手札113)Python+Dash快速web应用开发——表单控件篇(下)
    (数据科学学习手札112)Python+Dash快速web应用开发——表单控件篇(上)
    (数据科学学习手札111)geopandas 0.9.0重要新特性一览
    .NET Core 下使用 Apollo 配置中心
    .NET Core 下的爬虫利器
    .NET Core 集成JWT认证
    .NET Core 下收发邮件之 MailKit
  • 原文地址:https://www.cnblogs.com/suizhikuo/p/15507546.html
Copyright © 2011-2022 走看看