Java 集合中同步ArrayList和CopyOnWriteArrayList的区别
我们知道ArrayList是不同步的,如果多个线程试图同时修改ArrayList,那么最后的结果将是不确定的。因此,为了在多线程环境下实现线程安全,同步ArrayList是必须的。
为了创建List对象,我们一般都是创建List接口的对象,然后根据我们的要求创建List类,并在最近添加元素,进行访问和更新,而没有考虑到线程安全问题。这个概念很简单,同时也有点高级,因为我们看到大多数Java开发者在编写代码时并没有采用这种技术。
注意: 同步ArrayList是同步集合,而 CopyOnWriteArrayList是一个并发集合,因为它是在保持并发性的情况下产生的。
在ArrayList中实现同步的不同方法
ArrayList中的同步可以通过两种方式实现。
- 使用集合类的synchronizedList()方法
- 使用CopyOnWriteArrayList(COWAL)。
例子
输出
由于这两种方法都是用来实现Arraylist的线程安全的。问题出现了,何时使用COWAL,何时使用Collections类的synchronizedList()方法。这可以通过了解它们之间的区别来理解。 synchronized ArrayList和CopyOnWriteArrayList之间的主要区别来自于它们的性能、可扩展性以及它们如何实现线程安全。
当Collection.synchronizedList()已经存在时,为什么CopyOnWriteArrayList会出现?
所以答案很简单,因为最初,SynchronizedList被用于多线程环境,但它有一些限制。它所有的读写方法都是同步于列表对象本身的,也就是说,如果一个线程正在执行add()方法,它就会阻止其他想要获得迭代器的线程访问列表中的元素。另外,每次只允许一个线程对列表中的元素进行迭代,这是不高效的。这是很僵硬的。因此,需要一个更灵活的集合,允许。
- 多个线程同时执行读操作。
- 一个线程执行读操作,另一个线程同时执行写操作。
- 只有一个线程可以执行写操作,而其他线程可以同时执行读操作。
为了克服这些问题,最后,在 Java 5 中,引入了一组新的集合类,称为 并发集合 ,其中有 CopyOnWriteArrayList 。CopyOnWriteArrayList类是为了实现这种顺序写和并发读的功能。
让我们来讨论一下与这两个类相关的特性,它们之间有一条细微的区别,如下所示。
1.线程的锁定
CopyOnWriteArrayList 类的工作原理与它的名字一样,即写时复制(copy-on-write ),在读和写的操作中执行不同的操作。对于每一个写操作(add、set、remove等),它都会为列表中的元素制作一个新的副本,而对于读操作(get、iterator、listIterator等),它在一个不同的副本上工作。因此,在读操作中没有额外的开销,它的读操作比Collections.SynchronizedList()快。因此, COWAL比Synchronized List更适合于读取操作 。
2.写操作
对于ArrayList的写操作, COWAL的写操作比Collections.synchronizedList()慢, 因为它使用了Re-entrantLock。写入方法总是创建一个现有数组的副本,并在副本上进行修改,最后更新数组的易失性引用以指向这个新数组。因此,在写操作中,它有大量的开销。这就是为什么CopyOnWriteArrayList的写操作要比Collections.synchronizedList()慢。
3.修改过程中的行为
Synchronized List是一个快速失败的迭代器 ,也就是说,当一个线程在列表上迭代时,它将抛出ConcurrentModifcationException,而 CopyOnWriteArrayList是一个失败安全的迭代器 ,也就是说,即使一个线程在列表上迭代时,它也不会抛出ConcurrentModifcationException。
4.工作线程的数量
只有一个线程被允许在同步列表上操作,通过锁定整个列表对象,这影响了它的性能,因为其他线程在等待,而在 COWAL 的情况下,多个线程被允许在ArrayList上操作,因为它在单独的克隆副本上进行更新/修改操作,这 使得它的性能更快。
5.块内迭代
当迭代同步List时,确保在同步块内迭代,而在CopyOnWriteArrayList中,我们可以安全地在同步块外迭代。
什么时候使用SynchronizedList
- 因为在CopyOnWriteArrayList中,每一次更新/修改操作都会创建一个新的单独的克隆副本,JVM分配内存并将克隆副本与原始副本合并的开销很大。因此,在这种情况下,SynchronizedList是一个更好的选择。当Arraylist的大小很大时。
- 当Arraylist的大小很大时。
什么时候使用CopyOnWriteArrayList
- CopyOnWriteArrayList提供了无锁的读取,这意味着如果有更多的读者线程,并且写入发生率很低,那么性能会好很多。
- 当Arraylist的大小较小时。
SynchronizedList v/s CopyOnWriteArrayList
SynchronizedArrayList | CopyOnWriteArrayList |
---|---|
在Java 1.2版本中引入 | 在Java 1.5版本中被引入 |
当写操作多于读操作时,就应该使用它。 | 当读操作多于写操作时,应该使用它。 |
使用的迭代器是故障快速的。 | 使用的迭代器是故障安全的。 |
List的迭代必须存在于同步块内。 | 列表的迭代可以在同步块之外。 |
整个ArrayList被Synchronized Arraylist锁定,以便在读和写操作中保证线程安全。 | 整个ArrayList被SynchronizedArrayList锁定,以便在写操作中保证线程安全。 |
当ArrayList较大时,它是首选。 | 当ArrayList较小时,它是首选。 |