volatile关键字有什么用?

说道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的值。可以正常的结束线程。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值