问题概要
我在研究volatile关键字应用场景的时候,无意中发现了while在多线程情况下出现了“非正常输出“的情况。
问题分析
这段代码的意义是创建两个线程,分别执行test1()方法和test2()方法,其中标识全局变量(也就是两个线程共享的变量)flag初始化为false;为了使步骤更加清晰可见,我将test1()的初始化分为5步,在test1()也就是线程1要执行的方法结尾将flag标识为true;那么线程二若是捕捉到主内存中的flag由false变为true,while循环体中内容应该会被执行,可是结果是while中内容并没有被执行。
@Slf4j
public class VolatileExample3 {
volatile boolean flag=false;//定义初始化标识
public void test1() {
log.info("-----线程1开始执行-----");
for (int i=0;i<5;i++) {
try {
Thread.sleep(1000);
log.info("test1初始化任务第"+i+"步已完成...");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
flag = true;
log.info("初始化全部完成,flag变量已更改!");
}
public void test2() {
log.info("-----线程2开始执行-----");
while(flag) {
log.info("test2()方法执行完毕!");
break;
}
}
public static void main(String[] args) {
VolatileExample3 ve = new VolatileExample3();
ExecutorService executorService =Executors.newCachedThreadPool();
//线程1创建
executorService.execute(()->{
ve.test1();
});
//线程2创建
executorService.execute(()->{
ve.test2();
});
}
}
结果test2()的while中内容最终没有被执行:
我怀疑线程二没有捕捉到flag在主内存中的变化,当我将test2()方法改成以下死循环,不停的给我打印flag值时:
public void test2() {
log.info("-----线程2开始执行-----");
while(true) {
log.info("flag:{}",flag);
}
}
结果flag居然可以被捕捉到由false变为true,这说明线程一确实将自己工作内存的副本变量flag刷新到了主内存,并且线程2也更新了自己工作内存的值,而且我加了关键字volatile,当线程一更改了flag变量值,线程二应该马上就能看到修改后的值:
说明flag确实是变化了的,那就奇了怪了,为什么直接写while(flag)却不能由条件不成立到条件成立呢?
事实证明我是想多了,当我在while前后打出输出语句,如下:
public void test2() {
log.info("start");
while(flag) {
log.info("test2()方法执行完毕!");
}
log.info("end");
}
结果end居然执行了,突然想起来首次运行条件flag为false,那一定不走循环体,直接输出end,线程整个过程都结束了还谈什么循环:
INFO [pool-1-thread-2] - start
INFO [pool-1-thread-1] - -----线程1开始执行-----
INFO [pool-1-thread-2] - end
INFO [pool-1-thread-1] - test1初始化任务第0步已完成...
INFO [pool-1-thread-1] - test1初始化任务第1步已完成...
INFO [pool-1-thread-1] - test1初始化任务第2步已完成...
INFO [pool-1-thread-1] - test1初始化任务第3步已完成...
INFO [pool-1-thread-1] - test1初始化任务第4步已完成...
INFO [pool-1-thread-1] - 初始化全部完成,flag变量已更改!
日常用法应该是这样:
public void test2() {
log.info("-----线程2开始执行-----");
while(!flag) {
log.info("test2()方法等待flag变化中~");
}
log.info("test2()方法执行完毕!");
}
结果:
......
INFO [pool-1-thread-2] - test2()方法等待flag变化中~
INFO [pool-1-thread-2] - test2()方法等待flag变化中~
INFO [pool-1-thread-2] - test2()方法等待flag变化中~
INFO [pool-1-thread-2] - test2()方法等待flag变化中~
INFO [pool-1-thread-1] - test1初始化任务第4步已完成...
INFO [pool-1-thread-1] - 初始化全部完成,flag变量已更改!
INFO [pool-1-thread-2] - test2()方法等待flag变化中~
INFO [pool-1-thread-2] - test2()方法执行完毕!
看来基础还是需要巩固的啊。