volatile疑问记录

对java中volatile关键字的描述,主要是可见性有序性两方面。

一个很广泛的应用就是使得多个线程对共享资源的改动变得互相可见,如下:

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
public class TestVolatile extends Thread {
/*A*/
// public volatile boolean runFlag = true;
public boolean runFlag = true;

public boolean isRunFlag() {
return runFlag;
}

public void setRunFlag(boolean runFlag) {
this.runFlag = runFlag;
}

@Override
public void run() {
System.out.println("进入run");
while (isRunFlag()) {
/*B*/
// System.out.println("running");
}
System.out.println("退出run");
}

public static void main(String[] args) throws InterruptedException {
TestVolatile testVolatile = new TestVolatile();
testVolatile.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
testVolatile.setRunFlag(false);
System.out.println("main already set runflag to false");
new CountDownLatch(1).await();
}
}

在A处如果不将运行标记(runflag)设置成volatile,那么main线程对runflag的修改对于testVolatile线程将不可见。导致其一直不打印“退出run”这句。

但是如果在testVolatile线程的while()增加一句:B处打印语句,程序却达到了不使用volatile,修改也变得可见,不知道到底是什么原理。

只能大概估计是while()的执行过程中线程上下文进行了切换,使得重新去主存获取了runflag的最新值,从而退出了循环,暂时记录…

2017/3/8日更新
和群里面的朋友讨论了一下,发现同一份代码,不同的机器运行出了不一样的效果。又仔细翻阅了一下《effective java》,依稀记得当时好像遇到过这个问题,果然,在并发的第一张就对这个现象做出了解释。
关键就在于HotSpot Server VM对编译进行了优化,这种优化称之为提升(hoisting),结果导致了活性失败(liveness failure)

1
while (isRunFlag()) {}

会被优化成

1
2
3
if(isRunFlag()){
while(true)...
}

引用effective java这一节的原话:

简而言之,当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步
如果没有同步,就无法保证一个线程所做的修改可以被另一个线程获知。未能同步共享可变数据会造成程序的活性失败和安全性失败。这样的失败是难以调式的。他们可能是间歇性的,且与时间相关,程序的行为在不同的VM上可能根本不同,如果只需要线程之间的交互通信,而不需要互斥,volatile修饰符就是一种可以接受的同步形式,但是正确的使用它可能需要一些技巧。

分享到