2

以下代码是否被认为是线程安全的,即:写入列表是否保证发生在读取列表之前?我一直试图了解这在 Java 内存模型中是否被认为是安全的,但目前还不清楚。

通过基本的流程分析,看起来可以保证所有可能的线程在到达下面synchronized的循环之前都必须通过初始化程序块for,但是对该列表的迭代是否是确定性的和线程安全的?我不确定在使用下面的列表之前是否保证初始化。

假设这是类中唯一的方法。我知道在同步块内移动迭代可以保证线程安全,但我更想知道这个构造是否安全。

此外,假设列表永远不会逃脱类。

Java 内存模型在此处的 JLS 中进行了解释:http: //docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4

private List<Foo> list;
private final Object monitor = new Object();

public void bar() {
    synchronized (monitor) {
        if (list == null) {
            list = new ArrayList<>();
            list.add(...); // expensive operation
            list.add(...); // expensive operation
            list.add(...); // expensive operation
        }
    }

    for (Foo foo : list) {
        // do something with foo
    }
}
4

4 回答 4

7

当且仅当这是您在结构上修改列表的唯一位置时,它才是线程安全的。

如果您在其他地方修改列表(例如使用clear()),即使该其他地方也使用synchronized了,那么在您迭代列表时可以轻松修改列表。

如果您打算在其他任何地方修改列表,那么使用Collections.unmodifiableList()来确保(并记录)这一事实可能是一个好主意。

于 2013-02-13T15:40:37.297 回答
2

JLS #17.4.5保证:

监视器上的解锁发生在该监视器上的每个后续锁定之前。

它还保证同步块不能并发执行,不能用for循环重新排序。

因此,第一个到达并获得监视器的线程(我们称之为 T0)将初始化列表。当 T0 退出同步块时,线程内语义保证 for 循环将在 T0 中按预期执行。

随后到达的所有线程将等待监视器可用,获取它,并且由于上面的保证,将看到由 T0 初始化的列表(即不为空且已填充)。并且由于线程内语义,for 循环将按预期执行。

结论:如果您的列表没有在其他地方写入并且所有读取都在获取监视器后完成,那么您的代码是安全的。

于 2013-02-13T17:13:24.340 回答
1

如果您不再从该同步方法中修改您的列表,那么您公开的代码是安全的。现在,如果其他方法(不是 bar())使用相同的列表,那么您的代码是不安全的。此外,您应该声明final List list

于 2013-02-13T15:39:57.020 回答
1

synchronized(list)仅适用于其中包含的代码块。如果在您使用 for each 循环遍历列表时另一个线程修改了列表,您将遇到问题。

于 2013-02-13T15:41:04.680 回答