在同一函数中的synchronized()块中初始化后,Java集合能否在synchronized()之外安全使用?
以下代码是否被认为是线程安全的,即:写入列表是否保证在读取列表之前发生?我一直在试图理解这在Java内存模型中是否被认为是安全的,但还不清楚 通过基本流分析,似乎可以保证所有可能的线程在点击下面的在同一函数中的synchronized()块中初始化后,Java集合能否在synchronized()之外安全使用?,java,concurrency,thread-safety,Java,Concurrency,Thread Safety,以下代码是否被认为是线程安全的,即:写入列表是否保证在读取列表之前发生?我一直在试图理解这在Java内存模型中是否被认为是安全的,但还不清楚 通过基本流分析,似乎可以保证所有可能的线程在点击下面的for循环之前都必须通过synchronized初始化程序块,但是对该列表的迭代是否具有确定性和线程安全性?我不确定在使用下面的列表之前是否保证进行初始化 假设这是类中唯一的方法。我知道在同步块中移动迭代可以保证线程安全,但我更感兴趣的是知道这个构造是否安全 另外,假设列表从未逃过类 Java内存模型在
for
循环之前都必须通过synchronized
初始化程序块,但是对该列表的迭代是否具有确定性和线程安全性?我不确定在使用下面的列表之前是否保证进行初始化
假设这是类中唯一的方法。我知道在同步块中移动迭代可以保证线程安全,但我更感兴趣的是知道这个构造是否安全
另外,假设列表从未逃过类
Java内存模型在JLS中解释如下:
私有列表;
私有最终对象监视器=新对象();
公共空白栏(){
同步(监视器){
if(list==null){
列表=新的ArrayList();
list.add(…);//昂贵的操作
list.add(…);//昂贵的操作
list.add(…);//昂贵的操作
}
}
for(Foo-Foo:list){
//和福做点什么
}
}
如果您不再从同步方法中修改列表,那么您公开的代码是安全的。
现在,如果其他方法(不是bar())使用相同的列表,那么您的代码是不安全的。另外,您应该声明
最终列表它是线程安全的当且仅当这是您在结构上修改列表的唯一地方
如果您在其他地方修改列表(例如使用clear()
),即使其他地方也使用synchronized
,那么在迭代列表时也可以轻松修改列表
如果您不打算在其他任何地方修改列表,那么使用来确保(并记录)这一事实可能是一个好主意。同步的列表(list)
仅适用于其中包含的代码块。如果另一个线程在您使用for each循环遍历列表时修改列表,则会出现问题。保证:
监视器上的解锁发生在监视器上的每个后续锁定之前
它还保证同步块不能并发执行,也不能用for循环重新排序
因此,到达并获取监视器的第一个线程(我们称之为T0)将初始化列表。当T0退出同步块时,线程内语义保证for循环将按照T0中的预期执行
随后到达的所有线程将等待监视器可用,获取它,并且由于上面的保证,将看到由T0初始化的列表(即不为null并填充)。由于线程内语义,for循环将按预期执行
结论:如果您的列表没有在其他地方写入,并且所有读取都在获取监视器后完成,则您的代码是安全的。我已经对假设列表不会逃逸课堂的问题进行了澄清。请注意-我正在寻找JLS内存模型方面的答案!您可能希望将监视器
设为最终版
。我已更新了问题,以反映您在此处注意到的未初始化值,因为我不想分散对内存模型问题的注意力。@Sliphed:我已删除了答案的这一部分。
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
}
}