[英]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.