线程安全之volatile关键字

volatile 这个单词本意是易变的、易失的,那它和线程安全有什么关系呢?我们来看一段代码:

/**
 * 创建2个线程去同时访问一个共享变量rmb,第1个线程进行余额消费,第2个线程进行账户冻结,
 * 这里有个需求就是让第2个线程根据用户的输入来冻结账户,从而阻止线程1进行消费
 */
 
class Account{
    //账户余额
    private double rmb = Double.MAX_VALUE;
    //账户状态
    private int isUsable = 1; //1表示可用、2表示禁用
    //账户对象
    private static Account instance = new Account();

    //这里是单例模式,所以将构造器私有化
    private Account(){
    }

    public static Account getInstance(){
        return instance;
    }

    public double getRmb(){
        return rmb;
    }

    public void setRmb(double rmb){
        this.rmb = rmb;
    }

    public int getIsUsable() {
        return isUsable;
    }

    public void setIsUsable(int isUsable) {
        this.isUsable = isUsable;
    }

    public void consume(){
        rmb -= 100.0; //规定每次只能消费100
    }

}

public class Demo {
    public static void main(String[] args){
        //账户对象
        Account account = Account.getInstance();

        //线程1进行消费
        Thread t1 = new Thread( () -> {
            while(account.getIsUsable() == 1){ //如果账户可以使用就循环进行消费
                account.consume();
            }
            System.out.println("无法进行消费,账户被冻结!");
        },"线程1");
        t1.start();

        //线程2进行账户冻结
        Thread t2 = new Thread(() -> {
            Scanner scan = new Scanner(System.in);
            System.out.println("是否冻结账户?Y/N");
            if("Y".equalsIgnoreCase(scan.next())){
                account.setIsUsable(0);
            }

        },"线程2");
        t2.start();
    }
}

我们这里想得出的结果是在键盘输入y/Y之后,线程1检测到账户被冻结然后跳出循环打印提示信息。但是调试结果和我们预测的不一样,线程1并没有结束而是继续在运行。如下图:

在这里插入图片描述

这里的原因就涉及到Java内存可见性。

在这里插入图片描述

account.getIsUsable() == 1 对应的指令是两步:

  1. 把内存中变量的值读到CPU的寄存器中,称为load;
  2. 将寄存器中的值与1进行比较,根据比较结果决定下一步如何执行(条件跳转指令),称为cmp。

account.getIsUsable() == 1 作为循环判断的条件每秒钟可以执行百万次以上,站在Java编译器的视角,load操作相比cmp操作要慢的多,且这里一直在做重复读操作同时结果还都一样,因此为了提升执行效率,编译器做出了一个大胆的决定——不再重复的进行load了,将account.getIsUsable()获取到的变量值干脆直接放到CPU寄存器中,判断没有其他线程进行修改了。

这其实就是编译器优化在多线程的环境下产生了误判,一个线程对一个变量进行读取操作,同时另一个线程对这个变量进行修改,此时读到的值不一定是修改后的值,这个读线程没有感知到变量的变化,而实际上是有其他线程在修改的:

在这里插入图片描述

因此就需要程序员进行手动干预,我们通过给这个变量加上 volatile 关键字告诉编译器这个变量是易变的,你每次都需要从内存中去重新load这个变量的值,指不定啥时候就变了。

在这里插入图片描述

然后再重新执行程序,查看结果:

在这里插入图片描述
t2线程读取到键盘输入的确认指令后对账户状态进行修改,此时t1线程则知道了变量已被修改,跳出循环并打印提示信息。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值