Overview

参考 Github 项目 https://github.com/Hydrated-Water/How-To-Get-Visibility

该项目是一个简单的实验,记录并展示了如何通过一个看似没用空同步块解决可见性问题


Main

现有一个模型 Model,其中(列表)存储了一些(嵌套的)POJO对象。线程A对其中一些POJO对象的值进行修改,线程B能否获取最新值?


许多情况下不能,以下代码展示了这一结果

Main.java::test1()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void test1(){

Model model = new Model();
{
Entity entity = new Entity();
entity.setId(1L);
model.getEntities().add(entity);
Data data = new Data();
data.setX(0);
data.setY(0);
entity.setData(data);
}

new Thread(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException ignored) {

}
model.getEntities().get(0).getData().setX(1);
System.out.println("FOO");
}).start();

while(true){
if(model.getEntities().get(0).getData().getX()>0) break;
}

System.out.println("BAR");
}

主线程在大多数情况下无法输出BAR


许多方法可以解决上述问题,声明volatile字段、原子类、在POJO的settergetter方法上加锁等等

当有一个简单的方法可以避免大规模修改代码——一个空的synchronized语句:

Main.java::test2()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static final Object lock = new Object();

static void test2(){

Model model = new Model();
{
Entity entity = new Entity();
entity.setId(1L);
model.getEntities().add(entity);
Data data = new Data();
data.setX(0);
data.setY(0);
entity.setData(data);
}

new Thread(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException ignored) {

}
model.getEntities().get(0).getData().setX(1);
synchronized (lock){};
System.out.println("FOO");
try {
Thread.sleep(30000);
}
catch (InterruptedException ignored) {

}
}).start();

while(true){
synchronized (lock){};
if(model.getEntities().get(0).getData().getX()>0) break;
}

System.out.println("BAR");
}

Afterword

其实大多数情况下上述问题不会发生,因为常用的并发容器如CopyOnWriteArrayListCollections.synchronizedList(new ArrayList<>())的副作用也能避免可见性问题,但更加不可控