繁体   English   中英

在同一函数的synchronized()块中初始化之后,是否可以在synchronized()之外安全地使用Java集合?

[英]Can a Java collection be safely used outside of synchronized() after initializing in a synchronized() block in the same function?

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

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

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

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

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
    }
}

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

如果你在其他地方修改列表(例如使用clear() ), 即使其他地方也使用了synchronized ,那么在迭代它时可以很容易地修改列表。

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

JLS#17.4.5保证:

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

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

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

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

结论 :如果您的列表未写入其他位置并且所有读取都在获取监视器后完成,则您的代码是安全的。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM