java volatile关键字

1 引入

并发编程下,多线程修改变量,会出现线程间变量的不可见性。即多个线程访问共享变量,会出现一个线程修改变量的值后,其他线程看不到最新值的情况。

代码示例:

public class VolatileThread extends Thread {

    // 定义成员变量
    private boolean flag = false ;
    public boolean isFlag() { return flag;}

    @Override
    public void run() {

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 将flag的值更改为true
        this.flag = true ;
        System.out.println("flag=" + flag);

    }
}

public class VolatileThreadDemo {// 测试类
    
    public static void main(String[] args) {

        // 创建VolatileThread线程对象
        VolatileThread volatileThread = new VolatileThread() ;
        volatileThread.start();

        // main方法
        while(true) {
            if(volatileThread.isFlag()) {
                System.out.println("执行了======");
            }
        }
    }
}

运行结果:

falg=true

由此可见,VolatileThread线程中已将flag设置为true,但main()方法中始终没有读到,即出现了变量不可见问题。

2 变量不可见性出现的原因

在此之前,我们先来了解一下Java内存模型(java memory model, JMM),其屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。

JMM有如下规定

  • 所有的共享变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数,因为局部变量和方法参数是线程私有的,不存在竞争问题。
  • 每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存变量的副本拷贝。
  • 线程对变量的操作(读、写)都必须在工作内存中进行。
    线程不能直接读写主内存中的变量。
  • 不同线程之间也不能直接访问对方工作内存中的变量,线程间的变量的值的传递需要通过主内存中转来完成。

工作内存和主内存的关系图:
在这里插入图片描述

通过JMM我们可以分析得出现变量不可见的原因:

  1. VolatileThread线程从主内存读取到数据放入其对应的工作内存
  2. 将flag的值更改为true,但是这个时候flag的值还没有写会主内存
  3. 此时main方法读取到了flag的值为false
  4. 当VolatileThread线程将flag的值写回去后,但是main函数里面的while(true)调用的是系统比较底层的代码,速度快,快到没有时间再去读取主存中的值,所以while(true)读取到的值一直是false。(如果有一个时刻main线程从主内存中读取到了主内存中flag的最新值,那么if语句就可以执行,main线程何时从主内存中读取最新的值,我们无法控制)

3 变量不可见性的解决方法

3.1 加锁

   // main方法
   while(true) {
       synchronized (volatileThread) {
           if(volatileThread.isFlag()) {
               System.out.println("执行了======");
           }
       }
   }

某一个线程进入synchronized代码块前后,执行过程入如下:

  1. 线程获得锁
  2. 清空工作内存
  3. 从主内存拷贝共享变量最新的值到工作内存成为副本
  4. 执行代码
  5. 将修改后的副本的值刷新回主内存中
  6. 线程释放锁

总结:线程每次获得锁对象时会清空自己的工作内存,重写读取主内存的最新值。

3.2 使用volatile关键字

volatiled的三大特性:

  • 保证可见性。当一个线程修改共享变量的值,其他线程能够立即知道被修改了。
  • 不保证原子性。短时间内,两个线程工作内存中的变量值同时更新到主内存,会导致主内存中变量的值与预期不同。
  • 禁止指令重排。多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,结果无法预测
   private volatile boolean flag ;

工作原理:

  1. VolatileThread线程从主内存读取到数据放入其对应的工作内存
  2. 将flag的值更改为true,但是这个时候flag的值还没有写会主内存
  3. 此时main方法main方法读取到了flag的值为false
  4. 当VolatileThread线程将flag的值写回去后,失效其他线程对此变量副本
  5. 再次对flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中

总结: volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。

volatile不能用来修饰final类型的变量,因为没有意义

3.3 volatile与synchronized的区别

  • volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。
  • volatile保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);而synchronized是一种排他(互斥)的机制,
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰茶不冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值