Java,在数组中添加元素,并在其中并行循环
对不起,如果这是一个愚蠢的问题。但有人能解释一下在这种情况下会发生什么吗Java,在数组中添加元素,并在其中并行循环,java,concurrency,iteration,Java,Concurrency,Iteration,对不起,如果这是一个愚蠢的问题。但有人能解释一下在这种情况下会发生什么吗 List<Integer> scores = new Arraylist<>() ; scores = Collections.synchronizedList(scores) public void add(int element) { ... scores.add(element) ... } public String retrieve(int element)
List<Integer> scores = new Arraylist<>() ;
scores =
Collections.synchronizedList(scores)
public void add(int element) {
...
scores.add(element)
...
}
public String retrieve(int element) {
...
For (Integer e : scores)....
....
Return something
}
List scores=new Arraylist();
分数=
Collections.synchronizedList(分数)
公共void添加(int元素){
...
分数。添加(元素)
...
}
公共字符串检索(int元素){
...
对于(整数e:分数)。。。。
....
归还某物
}
让我们假设这个类是Singleton,分数是全局的。多线程可以同时添加和检索分数
在这个场景中,当启动for循环的同时线程正在添加(或从列表中删除一个元素)时,它会抛出一个并发的修改exeption吗
谢谢考虑到您编写示例的方式,糟糕的事情将会发生 您的
retrieve()
方法在synchronized
块中没有循环,并且您的两个方法都直接访问scores
,而不是使用Collections.synchronizedList()
方法返回的List
如果你看一下的API,你会注意到它说
为了保证串行访问,必须通过返回的列表来完成对支持列表的所有访问
用户在对返回的列表进行迭代时,必须手动同步该列表:
不遵循此建议可能会导致不确定性行为。
因此,您可能会得到一个ConcurrentModificationException
,或者发生一些奇怪的事情
编辑
即使您所有的访问都是通过同步的列表
,如果您在另一个线程中对列表进行迭代时修改该列表
,您仍然可以得到一个ConcurrentModificationException
。这就是为什么Collections.synchronizedList()
文档坚持要求您手动将迭代包装在一个块中,该块在它返回的列表上同步
API说
例如,通常不允许一个线程在另一个线程迭代集合时修改该集合。通常,在这些情况下,迭代的结果是未定义的。如果检测到此行为,某些迭代器实现(包括JRE提供的所有通用集合实现)可能会选择抛出此异常。这样做的迭代器称为fail-fast迭代器,因为它们快速、干净地失败,而不是在将来的不确定时间冒任意、不确定行为的风险
您的add方法不需要更改,但您的retrieve()
方法应该类似于:
public String retrieve(int element) {
// stuff
synchronized (scores) { // prevent scores from being modified while iterating
for (Integer e : scores) {
// looping stuff
}
}
// more stuff
return something;
}
示例程序
下面是一个演示安全访问与不安全访问行为的小示例程序:
public class Scratch {
private List<Integer> scores = Collections.synchronizedList(new ArrayList<Integer>());
public static void main(String[] args) throws Exception {
final Scratch s = new Scratch();
s.scores.add(1);
s.scores.add(2);
s.scores.add(3);
// keep adding things to the list forever
new Thread(new Runnable() {
@Override
public void run() {
try {
int i=100;
while (true) {
Thread.sleep(100);
s.scores.add(i++);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("This will run fine");
s.safeLoop();
System.out.println("This will cause a ConcurrentModificationException");
s.unsafeLoop();
}
public void safeLoop() throws InterruptedException {
synchronized (scores) {
for (int i : scores) {
System.out.println("i="+i);
Thread.sleep(100);
}
}
}
public void unsafeLoop() throws InterruptedException {
for (int i : scores) {
System.out.println("i="+i);
Thread.sleep(100);
}
}
}
公共类刮擦{
私有列表分数=Collections.synchronizedList(新的ArrayList());
公共静态void main(字符串[]args)引发异常{
最终划痕s=新划痕();
s、 分数。增加(1);
s、 分数。增加(2);
s、 分数。增加(3);
//永远把事情添加到列表中
新线程(newrunnable()){
@凌驾
公开募捐{
试一试{
int i=100;
while(true){
睡眠(100);
s、 分数。添加(i++);
}
}捕捉(中断异常e){
e、 printStackTrace();
}
}
}).start();
System.out.println(“这将正常运行”);
s、 safeLoop();
System.out.println(“这将导致ConcurrentModificationException”);
s、 unsafeLoop();
}
public void safeLoop()引发InterruptedException{
同步(分数){
对于(int i:分数){
System.out.println(“i=“+i”);
睡眠(100);
}
}
}
public void unsafeLoop()引发InterruptedException{
对于(int i:分数){
System.out.println(“i=“+i”);
睡眠(100);
}
}
}
很抱歉,这是个打字错误。让我们假设分数被分配到同步版本。那么,解决问题的好方法是使用ReadWriteLock吗?使用add中的write锁和retrieve?@Johny19中的read锁,我已经根据通过synchronized list的访问更新了我的答案。