CopyOnWriteArrayList 和 synchronizedList 的区别
Difference between CopyOnWriteArrayList and synchronizedList
据我了解,并发集合 类 优于同步集合,因为并发集合 类 不会锁定整个集合对象。相反,他们锁定了集合对象的一小部分。
但是当我检查 CopyOnWriteArrayList
的 add
方法时,我们正在获取对完整集合对象的锁定。那为什么 CopyOnWriteArrayList
比 Collections.synchronizedList
返回的列表更好呢?我在 CopyOnWriteArrayList
的 add
方法中看到的唯一区别是每次调用 add
方法时我们都在创建该数组的副本。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
1) CopyOnWriteArrayList
上的 get 和其他读取操作不同步。
2) CopyOnWriteArrayList
的迭代器永远不会 throws ConcurrentModificationException
而 Collections.synchronizedList
的迭代器可能会抛出它。
对于写入(添加)操作,CopyOnWriteArrayList 使用 ReentrantLock 并创建数据的备份副本,底层易失性数组引用仅通过 setArray 更新(在 setArray 之前对列表的任何读取操作将 return 添加前的旧数据)。此外,CopyOnWriteArrayList 提供快照故障安全迭代器并且不会在写入/添加时抛出 ConcurrentModifficationException。
But when I checked add method of CopyOnWriteArrayList.class, we are acquiring lock on complete collection object. Then how come CopyOnWriteArrayList is better than synchronizedList. The only difference I see in add method of CopyOnWriteArrayList is we are creating copy of that array each time add method get called.
- 不,锁不在整个 Collection 对象上。如上所述,它是一个 ReentrantLock 并且它不同于内部对象锁。
- add 方法将始终创建现有数组的副本并对副本进行修改,最后更新数组的 volatile 引用以指向这个新数组。这就是为什么我们有名称 "CopyOnWriteArrayList" - 当你写入它时进行复制。这也避免了 ConcurrentModificationException
As per my understanding concurrent collection classes preferred over synchronized collection because concurrent collection classes don't take lock on complete collection object. Instead it takes lock on small segment of collection object.
部分合集是这样,但不是全部。 Collections.synchronizedMap
locks the entire map around every operation, whereas ConcurrentHashMap
返回的映射仅针对某些操作锁定一个哈希桶,或者它可能对其他操作使用非阻塞算法。
对于其他集合,使用的算法和权衡是不同的。 Collections.synchronizedList
compared to CopyOnWriteArrayList
返回的列表尤其如此。正如您所指出的,synchronizedList
和 CopyOnWriteArrayList
在写操作期间都锁定了整个数组。那么为什么不同呢?
如果您查看其他操作,例如遍历集合中的每个元素,就会发现差异。 Collections.synchronizedList
的文档说,
It is imperative that the user manually synchronize on the returned list when iterating over it:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
Failure to follow this advice may result in non-deterministic behavior.
换句话说,迭代 synchronizedList
是 不是 线程安全的,除非您手动锁定。请注意,使用此技术时,此列表上其他线程的所有操作(包括迭代、获取、设置、添加和删除)都将被阻止。一次只有一个线程可以对此集合执行任何操作。
相比之下,CopyOnWriteArrayList
的文档说,
The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException
. The iterator will not reflect additions, removals, or changes to the list since the iterator was created.
此列表中其他线程的操作可以并发进行,但迭代不受任何其他线程所做更改的影响。因此,即使写操作锁定了整个列表,CopyOnWriteArrayList
仍然可以提供比普通 synchronizedList
更高的吞吐量。 (前提是读和遍历占写的比例很高。)
据我了解,并发集合 类 优于同步集合,因为并发集合 类 不会锁定整个集合对象。相反,他们锁定了集合对象的一小部分。
但是当我检查 CopyOnWriteArrayList
的 add
方法时,我们正在获取对完整集合对象的锁定。那为什么 CopyOnWriteArrayList
比 Collections.synchronizedList
返回的列表更好呢?我在 CopyOnWriteArrayList
的 add
方法中看到的唯一区别是每次调用 add
方法时我们都在创建该数组的副本。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
1) CopyOnWriteArrayList
上的 get 和其他读取操作不同步。
2) CopyOnWriteArrayList
的迭代器永远不会 throws ConcurrentModificationException
而 Collections.synchronizedList
的迭代器可能会抛出它。
对于写入(添加)操作,CopyOnWriteArrayList 使用 ReentrantLock 并创建数据的备份副本,底层易失性数组引用仅通过 setArray 更新(在 setArray 之前对列表的任何读取操作将 return 添加前的旧数据)。此外,CopyOnWriteArrayList 提供快照故障安全迭代器并且不会在写入/添加时抛出 ConcurrentModifficationException。
But when I checked add method of CopyOnWriteArrayList.class, we are acquiring lock on complete collection object. Then how come CopyOnWriteArrayList is better than synchronizedList. The only difference I see in add method of CopyOnWriteArrayList is we are creating copy of that array each time add method get called.
- 不,锁不在整个 Collection 对象上。如上所述,它是一个 ReentrantLock 并且它不同于内部对象锁。
- add 方法将始终创建现有数组的副本并对副本进行修改,最后更新数组的 volatile 引用以指向这个新数组。这就是为什么我们有名称 "CopyOnWriteArrayList" - 当你写入它时进行复制。这也避免了 ConcurrentModificationException
As per my understanding concurrent collection classes preferred over synchronized collection because concurrent collection classes don't take lock on complete collection object. Instead it takes lock on small segment of collection object.
部分合集是这样,但不是全部。 Collections.synchronizedMap
locks the entire map around every operation, whereas ConcurrentHashMap
返回的映射仅针对某些操作锁定一个哈希桶,或者它可能对其他操作使用非阻塞算法。
对于其他集合,使用的算法和权衡是不同的。 Collections.synchronizedList
compared to CopyOnWriteArrayList
返回的列表尤其如此。正如您所指出的,synchronizedList
和 CopyOnWriteArrayList
在写操作期间都锁定了整个数组。那么为什么不同呢?
如果您查看其他操作,例如遍历集合中的每个元素,就会发现差异。 Collections.synchronizedList
的文档说,
It is imperative that the user manually synchronize on the returned list when iterating over it:
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
Failure to follow this advice may result in non-deterministic behavior.
换句话说,迭代 synchronizedList
是 不是 线程安全的,除非您手动锁定。请注意,使用此技术时,此列表上其他线程的所有操作(包括迭代、获取、设置、添加和删除)都将被阻止。一次只有一个线程可以对此集合执行任何操作。
相比之下,CopyOnWriteArrayList
的文档说,
The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw
ConcurrentModificationException
. The iterator will not reflect additions, removals, or changes to the list since the iterator was created.
此列表中其他线程的操作可以并发进行,但迭代不受任何其他线程所做更改的影响。因此,即使写操作锁定了整个列表,CopyOnWriteArrayList
仍然可以提供比普通 synchronizedList
更高的吞吐量。 (前提是读和遍历占写的比例很高。)