浅析Collections.synchronizedList实现原理及如何做到线程安全、实现线程安全2种方式CopyOnWriteArrayList与Collections.synchronizedList的读写性能对比

一、Collections.synchronizedList 实现原理及如何做到线程安全

  大家都知道ArrayList并不是线程安全的,如果想要做到线程安全,我们可以使用 Collections.synchronizedList, 但是使用 Collections.synchronizedList后是否真的就线程安全了?

1、Collections.synchronizedList 原理

  我们先来看看Collections.synchronizedList 做了什么。

    从源码来看,SynchronizedList 就是在 List的操作外包加了一层 synchronize 同步控制。

2、加了 Collections.synchronizedList 后,为什么还需要使用 synchronized ?

首先我们看官方文档,可以发现, 当用户通过迭代器遍历返回列表时,必须手动同步:

It is imperative that the user manually synchronize on the returned list when traversing it via [Iterator]

List list = Collections.synchronizedList(new ArrayList());
      ...
  synchronized (list) {
      Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

  也就是说官方文档明确提出:对于 使用 Iterator 遍历列表时,Collections.synchronizedList 可能发生错误。

  那除了直接使用 Iterator 要加 synchronize 保证线程安全,还有什么情况会间接使用到 Iterator吗? 那就是 for each增强for循环;

  在使用 Iteratior 遍历的同时,异步修改List的结构,发现抛出了 ConcurrentModificationException 异常;

  那怎么解决呢?官方文档说的很清楚,我们在迭代器遍历返回列表时,增加手动同步处理,下面是IteratorRunnable 修改后 代码,仅仅是在外层加了 synchronized.

    static class IteratorRunnable implements Runnable {
       private List<Integer> list;
       public IteratorRunnable(List<Integer> synchronizeList) {
           this.list = synchronizeList;
       }
       @Override
       public void run() {
           while(true) {
               synchronized (list) {
                   for (Integer i : list) {
                       try {
                           TimeUnit.SECONDS.sleep(1);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       System.out.println(i + ",");
                   }
               }
           }
       }
   }

  从运行结果来看,增加了synchronized 后,不会出现ConcurrentModificationException异常了。

3、探究下for each Java的实现

  我们先来看看.class文件中for each

  看到这,我们就可以确定 ,其实JAVA中的增强for循环底层是通过iterator来实现的。

二、CopyOnWriteArrayList与Collections.synchronizedList的性能对比

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

1、ArrayList

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

  因此,在开发过程当中,ArrayList并不适用于多线程的操作。

2、Vector

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

3、Collections.synchronizedList & CopyOnWriteArrayList

  CopyOnWriteArrayList 和 Collections.synchronizedList 是实现线程安全的列表的两种方式。

  两种实现方式分别针对不同情况有不同的性能表现,其中 CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。

  而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。

  因此在不同的应用场景下,应该选择不同的多线程安全实现类。

4、Collections.synchronizedList

  Collections.synchronizedList的源码可知,其实现线程安全的方式是建立了list的包装类。

  其中,SynchronizedList对部分操作加上了synchronized关键字以保证线程安全。但其iterator()操作还不是线程安全的。

5、CopyOnWriteArrayList

  从字面可以知道,CopyOnWriteArrayList在线程对其进行些操作的时候,会拷贝一个新的数组以存放新的字段。

  其没有加任何同步关键字,根据以上写操作的代码可知,其每次写操作都会进行一次数组复制操作,然后对新复制的数组进行些操作,不可能存在在同时又读写操作在同一个数组上( 不是同一个对象),而读操作并没有对数组修改,不会产生线程安全问题。

  Java中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。

  其中setArray()操作仅仅是对array进行引用赋值。Java中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,是一个原子操作,几乎不需要CPU时间。

  在列表有更新时直接将原有的列表复制一份,并再新的列表上进行更新操作,完成后再将引用移到新的列表上。旧列表如果仍在使用中(比如遍历)则继续有效。如此一来就不会出现修改了正在使用的对象的情况(读和写分别发生在两个对象上),同时读操作也不必等待写操作的完成,免去了锁的使用加快了读取速度。

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

(1)写操作:在线程数目增加时CopyOnWriteArrayList的写操作性能下降非常严重,而Collections.synchronizedList虽然有性能的降低,但下降并不明显。

(2)读操作:在多线程进行读时,Collections.synchronizedList和CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加显著。

  结论: 

  CopyOnWriteArrayList,发生修改时候做 copy,新老版本分离,保证读的高性能,适用于以读为主,读操作远远大于写操作的场景中使用,比如缓存。

  而Collections.synchronizedList则可以用在CopyOnWriteArrayList不适用,但是有需要同步列表的地方, 读写操作都比较均匀的地方。

原文地址:https://www.cnblogs.com/goloving/p/15248448.html