📜  Java集合| Synchronized ArrayList 和 CopyOnWriteArrayList 的区别

📅  最后修改于: 2021-09-15 01:51:49             🧑  作者: Mango

由于ArrayList不是同步的,如果多个线程同时尝试修改一个 ArrayList,那么最终的结果将是不确定的。因此同步 ArrayList 是在多线程环境中实现线程安全的必要条件。

Arraylist 的这种同步可以通过两种方式完成:

  1. 使用 Collections.synchronizedList()
  2. 使用 CopyOnWriteArrayList (COWAL)。

由于两者都用于在 Arraylist 中实现线程安全,问题出现了,何时使用 COWAL,何时使用 Collection.synchronizedList()。这可以通过了解它们之间的差异来理解。同步 ArrayList 和 CopyOnWriteArrayList 之间的主要区别在于它们的性能、可伸缩性以及它们如何实现线程安全。

当 Collection.synchronizedList() 已经存在时,为什么 CopyOnWriteArrayList 会出现

最初,SynchronizedList 用于多线程环境,但它有一些限制。它的所有读取和写入方法都在列表对象本身上同步,即如果一个线程正在执行 add() 方法,它会阻止其他想要让迭代器访问列表中元素的线程。此外,一次只允许一个线程迭代列表的元素,这是低效的。那是相当僵硬的。

因此需要一个更灵活的集合,它允许:

  1. 多个线程并发执行读取操作。
  2. 一个线程同时执行读操作和另一个执行写操作。
  3. 只有一个线程可以执行写操作,而其他线程可以同时执行读操作。

为了克服这些问题,最终在Java 5 中,引入了一组名为Concurrent Collections的新集合类,其中包含CopyOnWriteArrayList 。 CopyOnWriteArrayList 类旨在启用此类顺序写入和并发读取功能。

它们之间的主要区别是:

  1. 线程锁定:同步列表锁定整个列表以在读取或写入操作期间提供同步和线程安全,而CopyOnWriteArrayList在这些操作期间不锁定整个列表。

    CopyOnWriteArrayList 类根据其名称工作,即写入时复制,它为读取和写入操作执行不同的操作。对于每个写操作(添加、设置、删除等),它都会为列表中的元素创建一个新副本。对于读取操作(get、iterator、listIterator 等),它适用于不同的副本。因此在读取操作期间没有额外的开销,并且其读取操作比 Collections.SynchronizedList() 更快。因此, COWAL 比同步列表更适合读取操作。

  2. 写操作:对于 ArrayList 中的写操作, COWAL 写操作比 Collections.synchronizedList() 慢,因为它使用 Re-entrantLock。 write 方法将始终创建现有数组的副本并对副本进行修改,然后最终更新数组的易失性引用以指向这个新数组。因此,它在写操作期间具有大量开销。这就是 CopyOnWriteArrayList 写操作比 Collections.synchronizedList() 慢的原因。
  3. 修改期间的行为:同步列表是一个快速失败的迭代器,即当一个线程对其进行迭代时,它会在列表被修改时抛出 ConcurrentModifcationException 而CopyOnWriteArrayList 是一个故障安全迭代器,即它不会抛出 ConcurrentModifcationException 即使列表是当一个线程迭代它时修改。
  4. 工作线程数:只允许一个线程对同步列表进行操作,通过锁定影响其性能的完整列表对象,因为其他线程正在等待,而在COWAL 的情况下,允许多个线程对 ArrayList 进行操作,因为它适用于用于更新/修改操作的单独克隆副本,这使其性能更快
  5. 在块内迭代:在迭代同步列表时,确保在同步块内迭代,而在 CopyOnWriteArrayList 中,我们可以安全地在同步块外迭代。

    何时使用 SynchronizedList?

    1. 由于在 CopyOnWriteArrayList 中为每个更新/修改操作创建了一个新的单独克隆副本,并且 JVM 上存在开销以分配内存并将克隆副本与原始副本合并。因此,在这种情况下 SynchronizedList 是更好的选择。
    2. 当 Arraylist 的大小很大时。

      何时使用 CopyOnWriteArrayList?

      1. CopyOnWriteArrayList 提供无锁读取,这意味着如果有更多读取器线程并且写入发生率很低,则性能会更好。
      2. 当 Arraylist 的大小较小时。

      SynchronizedList 与 CopyOnWriteArrayList

      SynchronizedList CopyOnWriteArrayList
      It locks the whole list for thread-safety during both read or write operation. It locks the list during write operation only, so no lock during read operation therefore, multiple threads executing read operations concurrently.
      It is a fail-fast iterator. It is a fail-safe iterator.
      It is Introduced in Java 1.2 version. It is Introduced in Java 1.5 version.
      Iteration of list should be inside synchronized block otherwise it will face non-deterministic behaviour. It can safely iterate outside the synchronized block.
      If any other thread tries to modify the list while one thread iterating that list, then it will throw ConcurrentModificationException. It doesn’t allow modifying the list while traversing, and it will not throw ConcurrentModificationException if the list is being modified by other thread during the traversal.
      It is best to use when arraylist is large and write operation are greater than read operation in list. It is best to use when ArrayList is small or read operation are greater then write operation.