Java 同步集合 - 值是多少?

Java Synchronized Collections - what is the value?

以下代码非常非常快地触发了 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();
    }
}

...而以下行为应有:

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();
    }
}

...我的理解是同步集合是为了在这种情况下防止 ConcurrentModificationExceptions(但显然它们不会)。

鉴于此:我应该在哪里使用这些?

两件事:

  1. 如果您需要对 ArrayList 进行同步访问,则应改用 Vector。它做同样的事情,但它的方法是同步的。
  2. 在你的例子中,第二个代码片段有效,因为你在两个线程中同步同一个对象 LIST

在第一个代码片段中,您没有按照 synchronizedListdocumentation 中的说明进行操作:

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

在另一个线程中,您是通过原始 LIST 添加到列表,而不是“返回列表”。 LIST 只是一个普通的 ArrayList 并且在其上调用 add 不会获取任何锁或类似的东西,因此 add 仍然可以在迭代进行时成功调用进步。

如果你这样做了:

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();
}

那么它就不会抛出 CME。当您在同步列表上调用 add 时,它会尝试获取 LIST 上的内部锁。如果迭代正在进行,那么锁已经被另一个线程持有(因为你在那里做了 synchronized (LIST) { ... }),所以它会等到迭代结束。将此与第二个代码片段进行比较,请注意这如何避免您在 add 调用周围编写额外的 synchronized (LIST) {} 块。