记一次多线程下使用while出现的问题

问题概要

我在研究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()方法执行完毕!

看来基础还是需要巩固的啊。

原文地址:https://www.cnblogs.com/xusp/p/12696127.html