Java,在数组中添加元素并同时循环遍历它

Java, adding elements in an array an concurrently looping through it

抱歉,如果这是一个愚蠢的问题。但是有人可以解释一下在这种情况下会发生什么吗?

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 
} 

让我们假设这个 class 是一个单例并且分数是全局的。多线程可以同时添加和检索分数

在这种情况下,当开始 for 循环并且同时线程正在添加(或从列表中删除元素)时,它会抛出并发修改异常吗?

谢谢

考虑到您编写示例的方式,不好的事情会发生。

您的 retrieve() 方法在 synchronized 块中没有循环,并且 您的两个 方法都直接访问 scores ,而不是使用 Collections.synchronizedList() 方法返回的 List

如果你看一下 Collections.synchronizedList() 的 API,你会发现上面写着

In order to guarantee serial access, it is critical that all access to the backing list is accomplished through the returned list.

It is imperative that the user manually synchronize on the returned list when iterating over it:

Failure to follow this advice may result in non-deterministic behavior.

所以你可能得到ConcurrentModificationException,否则可能会发生其他奇怪的事情。

编辑

即使您的所有访问都是通过同步的 List,如果您在修改 List 的同时在另一个中迭代它,您仍然可能最终得到 ConcurrentModificationException 抛出给您线。这就是为什么 Collections.synchronizedList() 文档坚持要您手动将迭代包装在一个块中,该块在 List 上同步 returns.

ConcurrentModificationException 的 API 说

For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances. Some Iterator implementations (including those of all the general purpose collection implementations provided by the JRE) may choose to throw this exception if this behavior is detected. Iterators that do this are known as fail-fast iterators, as they fail quickly and cleanly, rather that risking arbitrary, non-deterministic behavior at an undetermined time in the future.

您的添加方法不需要更改,但您的 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);
        }
    }
}