ArrayList和LinkedList是非线程安全的数组集合, 在多线程的情况下,会导致并发问题。
    Vector和Collections.synchronizedList能够追加或者删除元素时能够实现线程安全,但是使用迭代器遍历时,仍然是非线程安全的。假设两个线程分别在做数组元素的追加和遍历,在创建时,通过synchronized关键字对该对象加锁,使得同时只有一个线程能够修改该对象。与此同时,在遍历的时候,由于操作的是同一个对象,对象的modCount已经发生变化,导致产生并发操作异常。
    CopyOnWriteArrayList克服了上面的问题。CopyOnWriteArrayList在追加或者修改元素时,先是克隆出来一个新的数组,然后修改新的数组,最后用新的数组替换老的数组。在使用迭代器遍历时,首先保存一个快照,然后遍历该快照,在整个遍历过程中,遍历的始终是迭代器初始化时的数组。由此CopyOnWriteArrayList每次写操作都会实例化新的数组,对于频繁的写操作性能太差。文档接口使用说明写的非常简洁明了:

    This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don’t want to synchronize traversals, yet need to preclude interference among concurrent threads. 通常开销太高,但是当遍历操作的数量远远超过其他写操作时,效率可能更高。当你不能实现同步遍历或不需要时,它是有用的,但需要排除并发线程。

    • Vector是线程安全的集合,但如果在业务层如果使用不当,仍然会存在线程安全问题。

      1. private static Vector<String> vector = new Vector<String>();
      2. private static int counter = 100;
      3. static {
      4. for (int i = 0; i < counter; i++) {
      5. vector.add(String.valueOf(i));
      6. }
      7. }
      8. public static void test() {
      9. Thread t1 = new Thread(new Runnable() {
      10. public void run() {
      11. for (String s : vector) {
      12. System.out.println(s);
      13. }
      14. }
      15. });
      16. t1.setName("print thread");
      17. Thread t2 = new Thread(new Runnable() {
      18. public void run() {
      19. for (int i = 0; i < counter; i++) {
      20. vector.remove(i);
      21. }
      22. }
      23. });
      24. t2.setName("remove thrad");
      25. t1.start();
      26. t2.start();
      27. }

      image.png

    原因是迭代器在遍历元素时,并非线程安全。

    参考文档:
    https://xxgblog.com/2016/04/02/traverse-list-thread-safe/