Volatile 解决线程可见性的问题

有如下一段程序代码:

public class Demo {
          
   
    private static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
          
   
        Thread thread = new Thread(()->{
          
   
            int i=0;
            while(!stop){
          
   
                i++;
            }
            System.out.println("the thread is stopped.i="+i);
        });

        thread.start();
        Thread.sleep(1000);
        stop = true;
    }
}

执行后,没有任何输出。线程却不停止。 说明while循环仍在执行,那么stop的值仍为false,说明stop=true 对while并没有生效。 这就是多线程的可见性问题。 就是我们在外层将值改变,子线程没有看到变化。 如果要解决它,可以使用volatile关键字。

public class Demo {
          
   
    private volatile static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
          
   
        Thread thread = new Thread(()->{
          
   
            int i=0;
            while(!stop){
          
   
                i++;
            }
            System.out.println("the thread is stopped.i="+i);
        });

        thread.start();
        Thread.sleep(1000);
        stop = true;
    }
}

这次的结果为: 除了使用volatile关键字外, 如果在循环体while 中加入 Thread.sleep(0); 或者 System.out.println("the i="+i); 也可以停止线程。

为什么 Thread.sleep可以停止线程? 加Thread.sleep(0) 可以停止线程的原因是: 这是因为在hotspot的server端虚拟机中,做了深度优化(client版本就没有这个问题)。

C:Users86137>java -version
java version "1.8.0_361"
Java(TM) SE Runtime Environment (build 1.8.0_361-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.361-b09, mixed mode)

JIT 做了如下的优化:

while(!stop){
          
   
	    i++;
	    try {
          
   
	        Thread.sleep(1000);
	    } catch (InterruptedException e) {
          
   
	        e.printStackTrace();
	    }
	}
	//-->
	if(!stop){
          
   
		while(true){
          
   
			i++;
		}
	}

volatile可以禁止JIT的优化过程。

除了volatile,我们可以通过VM参数来设置JIT禁止优化: 首先我们把代码中private tatic boolean stop = false; 中的volatile去掉; 然后在执行的VM参数中配置-Djava.compiler=NONE 就会发现 while中的stop生效了,线程会停下来。

为什么print可以导致循环结束? print中包括两个步骤,1.IO操作;2.Synchronized。 Synchronized的释放,会将工作内存中的操作同步到主内存。所以stop最新的值会被while(!stop)读取到。 我们可以从如下示例中看到这个现象:

while(!stop){
          
   
	    i++;
	    synchronized (Demo.class){
          
   
	        
	    }
	}

加了一个Synchronized的锁,会发现线程开启执行后,会停下来。

而IO操作会涉及到与内存、硬盘的交互,它会阻塞线程。 比如:

while(!stop){
          
   
        i++;
        new File("test.java");
    }

加了IO操作,也会使线程停下来。

经验分享 程序员 微信小程序 职场和发展