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的访问更新了我的答案。