[英]Java Synchronized Collections - what is the value?
The following code triggers a ConcurrentModificationException very, very quickly:以下代码非常非常快地触发了 ConcurrentModificationException:
import java.util.*;
public class SynchFail {
static List<Integer> LIST = new ArrayList<Integer>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
LIST.add(1);
}
}}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
List<Integer> syncList = Collections.synchronizedList(LIST);
synchronized(syncList) {
for (Integer thisInt : syncList) {
}
}
}
}}).start();
}
}
... whereas the following behaves as it should: ...而以下行为应如此:
import java.util.*;
public class SynchSucceed {
static List<Integer> LIST = new ArrayList<Integer>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized(LIST) {
LIST.add(1);
}
}
}}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized(LIST) {
for (Integer thisInt : LIST) {
}
}
}
}}).start();
}
}
... my understanding was that synchronized collections were to prevent ConcurrentModificationException
s in situations like this (but clearly they do not). ...我的理解是,同步的 collections 是为了防止在这种情况下发生
ConcurrentModificationException
(但显然他们没有)。
Given this: where should I make use of these?鉴于此:我应该在哪里使用这些?
In the first code snippet, you have not followed the instructions in the documentation of synchronizedList
:在第一个代码片段中,您没有遵循
synchronizedList
文档中的说明:
In order to guarantee serial access, it is critical that all access to the backing list is accomplished through the returned list.
为了保证串行访问,对后备列表的所有访问都通过返回的列表完成是至关重要的。
In the other thread, you are adding to the list via the original LIST
, not the "returned list".在另一个线程中,您通过原始
LIST
添加到列表中,而不是“返回列表”。 LIST
is just a normal ArrayList
and calling add
on it won't acquire any locks or anything like that, so add
could still be successfully called while the iteration is in progress. LIST
只是一个普通的ArrayList
并且在其上调用add
不会获得任何锁或类似的东西,因此在迭代过程中仍然可以成功调用add
。
If you did:如果你这样做了:
final static List<Integer> LIST = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
LIST.add(1);
}
}}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized(LIST) {
for (Integer thisInt : LIST) {
}
}
}
}}).start();
}
Then it wouldn't throw a CME.然后它不会抛出 CME。 When you call
add
on a synchronised list, it tries to acquire the intrinsic lock on LIST
.当您在同步列表上调用
add
时,它会尝试获取LIST
上的内在锁。 If iteration is in progress, the lock would have been already held by the other thread (since you did synchronized (LIST) {... }
there), so it will wait until the iteration is over.如果迭代正在进行,则锁可能已经被另一个线程持有(因为你在那里做了
synchronized (LIST) {... }
),所以它会等到迭代结束。 Compare this with the second code snippet, and notice how this saves you from writing an extra synchronized (LIST) {}
block around the add
call.将此与第二个代码片段进行比较,并注意这如何使您免于在
add
调用周围编写额外的synchronized (LIST) {}
块。
Couple of things:几件事:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.