Java集合中同步ArrayList和CopyOnWriteArrayList的区别
众所周知,ArrayList 是不同步的,如果多个线程同时尝试修改一个 ArrayList,那么最终的结果将是不确定的。因此,为了在多线程环境中实现线程安全,必须同步 ArrayList。
为了制作 List 对象,我们通常会创建 List 接口的对象,并根据我们的要求制作 List 类,并在最近添加元素并进行访问、更新而无需考虑线程安全。这个概念很简单,同时也有点高级,因为大多数Java开发人员在编写代码时并没有实践这种技术。
Note: Synchronized ArrayList is synchronized collection while CopyOnWriteArrayList is an concurrent collection as it is made with keeping concurrency.
在 ArrayList 中实现同步的不同方式
Arraylist 中的同步可以通过两种方式实现:
- 使用 Collections 类的 synchronizedList() 方法
- 使用 CopyOnWriteArrayList (COWAL)
例子
Java
// Java Program to Illustrate Synchronized ArrayList
// Using synchronizedList() Method
// Importing required classes
import java.util.* ;
// Main class
// SynchronizedArrayList
class GFG {
// Main driver method
public static void main(String[] args) {
// Creating an empty ArrayList of string type
// By default, non - synchronized List
List sal = new ArrayList();
// Adding elements to above List
// using add() method
sal.add("Geeks");
sal.add("for");
sal.add("Geeks");
sal.add("Computer");
sal.add("Science");
sal.add("Portal");
// Printing the above non-synchronised List
System.out.println(sal);
// Synchronizing above List
// using SynchronizedList() method
Collections.synchronizedList(sal);
// Synchronized block to
// avoid non-deterministic behavior
synchronized (sal) {
// Using iterators to iterate over elements
Iterator itrobj = sal.iterator();
// Holds true till there is single element remaining
while (itrobj.hasNext()) {
// Printing elements
// using next() method
System.out.println(itrobj.next());
}
}
}
}
输出:
由于这两种方式都用于在 Arraylist 中实现线程安全。问题出现了,何时使用 COWAL 以及何时使用 Collections 类的 synchronizedList() 方法。这可以通过了解它们之间的差异来理解。 synchronized ArrayList 和 CopyOnWriteArrayList 之间的主要区别在于它们的性能、可伸缩性以及它们如何实现线程安全。
当 Collection.synchronizedList() 已经存在时,为什么 CopyOnWriteArrayList 会存在?
所以答案很简单,因为 SynchronizedList 最初是在多线程环境中使用的,但它有一些限制。它的所有读取和写入方法都在列表对象本身上同步,即,如果一个线程正在执行 add() 方法,它会阻塞其他想要让迭代器访问列表中元素的线程。此外,一次只允许一个线程迭代列表的元素,这是低效的。那是相当僵硬的。因此需要一个更灵活的集合,它允许:
- 多个线程同时执行读取操作。
- 一个线程执行读操作,另一个线程同时执行写操作。
- 只有一个线程可以执行写操作,而其他线程可以同时执行读操作。
为了克服这些问题,最后,在Java 5中,引入了一组名为Concurrent Collections的新集合类,其中包含CopyOnWriteArrayList 。 CopyOnWriteArrayList 类旨在启用此类顺序写入和并发读取功能。
让我们讨论与它们相关的特征,这些特征在它们之间产生了一条细线,如下所示:
1. 线程锁定
Synchronized List锁定整个列表以在读取或写入操作期间提供同步和线程安全,而CopyOnWriteArrayList在这些操作期间不会锁定整个列表。
CopyOnWriteArrayList 类根据其名称工作,即写时复制,它为读取和写入操作执行不同的操作。对于每个写入操作(添加、设置、删除等),它都会为列表中的元素创建一个新副本。对于读取操作(get、iterator、listIterator 等),它适用于不同的副本。因此在读取操作期间没有额外的开销,并且它的读取操作比 Collections.SynchronizedList() 更快。因此, COWAL 比同步列表更适合读取操作。
2.写操作
对于 ArrayList 中的写操作, COWAL 写操作比 Collections.synchronizedList() 慢,因为它使用了 Re-entrantLock。 write 方法将始终创建现有数组的副本并对副本进行修改,然后最终更新数组的 volatile 引用以指向这个新数组。因此,它在写入操作期间具有巨大的开销。这就是为什么 CopyOnWriteArrayList 写操作比 Collections.synchronizedList() 慢的原因。
3. 修改期间的行为
Synchronized List 是一个快速失败的迭代器,即当一个线程对其进行迭代时,当列表被修改时它会抛出 ConcurrentModifcationException 而CopyOnWriteArrayList 是一个故障安全迭代器,即即使当一个线程修改列表时它也不会抛出 ConcurrentModifcationException正在迭代它。
4. 工作线程数
只允许一个线程在同步列表上操作,通过锁定影响其性能的完整列表对象,因为其他线程正在等待,而在COWAL的情况下,允许多个线程在 ArrayList 上操作,因为它在单独的克隆上工作复制用于更新/修改操作,使其性能更快。
5. 块内迭代
在迭代同步列表时,请确保在同步块内进行迭代,而在 CopyOnWriteArrayList 中,我们可以安全地在同步块外进行迭代。
何时使用 SynchronizedList?
- 由于在每个更新/修改操作的 CopyOnWriteArrayList 中,都会创建一个新的单独克隆副本,并且 JVM 分配内存并将克隆副本与原始副本合并会产生开销。因此,在这种情况下,SynchronizedList 是一个更好的选择。当 Arraylist 的大小很大时。
- 当 Arraylist 的大小很大时。
何时使用 CopyOnWriteArrayList?
- CopyOnWriteArrayList 提供无锁读取,这意味着如果有更多读取器线程并且写入发生率很低,则性能会更好。
- 当 Arraylist 的大小较小时。
SynchronizedList 与 CopyOnWriteArrayList
SynchronizedArrayList | CopyOnWriteArrayList |
---|---|
It was introduced in Java version 1.2 | It was introduced in Java version 1.5 |
It should be used when there are more write operations over-read operations. | It should be used when there are more read operations than write operations. |
The iterator used is fail-fast. | The iterator used is fail-safe. |
The iteration of List has to be there inside the synchronized block. | The iteration of the list can be outside the synchronized block. |
The whole ArrayList is locked by Synchronized Arraylsit for thread safety during read and write operations. | The whole ArrayList is locked by SynchronizedArrayList for thread safety during the write operations only. |
It is preferred when ArrayList is larger. | It is preferred when ArrayList is smaller. |