说道volatile关键字,大家都不陌生,它可以使一个变量在多个线程中可见;
但是volatile并不能保证多个线程共同修改一个变量时所带来不一致的问题,即volatile只能保证可见性,不能保证原子性。
如果既要保证可见性又要保证原子性可以使用synchronized。
线程1,线程2都用到一个变量,java默认是线程1中保留一份copy,这样如果线程2修改了该变量,则线程1未必知道。
要了解可见性,我们得先来了解一下 Java 内存模型。
Java 内存模型(Java Memory Model,简称 JMM)描述了 Java 程序中各种变量(线程之间的共享变量)的访问规则,以及在 JVM 中将变量存储到内存===》从内存中读取变量的底层细节。
要知道,所有的变量都是存储在主内存中的,每个线程会有自己独立的工作内存,里面保存了该线程使用到的变量副本(主内存中变量的一个拷贝)。见下图。
package com.example.demo1.threadTest;
/**
* @desc 测试volatile
* @author chen-pown
* @date 2020-06-05
*/
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo();
Thread thread = new Thread(demo::check,"thread");
//启动线程
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改共享变量
demo.flag=false;
System.out.println("主线程结束");
}
static class ThreadDemo{
//共享变量
boolean flag = true;
public void check(){
//给子线程设置循环体
while(flag){
}
//子线程结束标识
System.out.println("ThreadDemo结束");
}
}
}
我们建立了一个只有flag
成员变量的内部类ThreadDemo
,在main方法里面,我们创建了一个子线程,执行ThreadDemo
里面的check
方法;
我们可以看到check()
里面是有一个死循环while
的,在flag
变量没有变成false之前是一直死循环的,在主线程里面我们对flag
进行修改,看看结果如何:
主线程已经结束了,flag
的值也修改了,但是子线程却一直没有结束,说明flag
字段的值在多个线程中是不共享的。
下面我们给flag
加上volatile
关键字:
//共享变量
volatile boolean flag = true;
我们发现程序完美的执行结束了。说明主线程里面的主内存flag值已经被同步到子线程内存里面了。
但这是否就意味着,如果我不加volatile
子线程就永远无法获取到主内存中的flag值?
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
我们去掉主线程里面的这段代码去掉,再把volatile
也去看看:
/**
* @desc 测试volatile
* @author chen-pown
* @date 2020-06-05
*/
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo();
Thread thread = new Thread(demo::check,"thread");
//启动线程
thread.start();
//修改共享变量
demo.flag=false;
System.out.println("主线程结束flag="+demo.flag);
}
static class ThreadDemo{
//共享变量
boolean flag = true;
public void check(){
//给子线程设置循环体
while(flag){
}
//子线程结束标识
System.out.println("ThreadDemo结束");
}
}
}
竟然也是没问题的?
这说明,jvm系统自己也会定时将主内存中的flag值,刷新到子线程内存中。但具体什么时候刷新呢?
/**
* @desc 测试volatile
* @author chen-pown
* @date 2020-06-05
*/
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo();
Thread thread = new Thread(demo::check,"thread");
//启动线程
thread.start();
System.out.println("主线程修改变量");
//修改共享变量
demo.flag=false;
System.out.println("主线程结束flag="+demo.flag);
}
static class ThreadDemo{
//共享变量
boolean flag = true;
public void check(){
System.out.println("ThreadDemo开始");
//给子线程设置循环体
while(flag){
}
//子线程结束标识
System.out.println("ThreadDemo结束");
}
}
}
我们加上打印信息在执行一次看下结果:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
我们在主线程代码里面加上睡眠时间再看一下。
最后flag前加上volatile试一下:
1): 可以发现flag前不添加volatile,主线程也不睡眠时,在子线程执行前,主线程已经执行结束了,flag已经更新为false,所以子线程没有陷入死循环。
2):当主线程睡眠,flag也没有加volatile修饰,在主线程flag变量变为false前,子线程已经开始执行,while()里面也没有方法体,jvm没有空间去更新子线程里面的flag值,所以陷入了死循环。
3):在添加了volatile关键字后,主线程的flag值被立即修改进子线程内存中,所以实现了参数共享。
如果我们在子线程while里面添加,打印信息,System.out.print()
/**
* @desc 测试volatile
* @author chen-pown
* @date 2020-06-05
*/
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo();
Thread thread = new Thread(demo::check,"thread");
//启动线程
thread.start();
System.out.println("主线程修改变量");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改共享变量
demo.flag=false;
System.out.println("主线程结束flag="+demo.flag);
}
static class ThreadDemo{
//共享变量
boolean flag = true;
public void check(){
System.out.println("ThreadDemo开始");
//给子线程设置循环体
while(flag){
System.out.println("ThreadDemo执行中");
}
//子线程结束标识
System.out.println("ThreadDemo结束");
}
}
}
执行一下:
发现在循环了很久之后还是可以正常结束的,因为有了方法体的执行,jvm便有了空闲去同步子线程flag的值。可以正常的结束线程。