volatile的原理解说

volatile关键字在Java中用于实现轻量级的线程同步,确保被修饰的变量对所有线程可见。然而,它并不保证原子性,例如在`start++`这样的操作中,可能出现多个线程同时读写导致结果不一致。因此,当需要原子性保障时,仍需借助锁或其他同步机制。
摘要由CSDN通过智能技术生成

volatile也是互斥同步的一种实现,不过它非常的轻量级。

volatile有条关键的语义:

  • 保证被volatile修饰的变量对所有线程都是可见的

要理解volatile关键字,我们得先从Java的线程模型开始说起。如图所示:

Java内存模型规定了所有字段(这些字段包括实例字段、静态字段等,不包括局部变量、方法参数等,因为这些是线程私有的,并不存在竞争)都存在主内存中,每个线程会 有自己的工作内存,工作内存里保存了线程所使用到的变量在主内存里的副本拷贝,线程对变量的操作只能在工作内存里进行,而不能直接读写主内存,当然不同内存之间也 无法直接访问对方的工作内存,也就是说主内存时线程传值的媒介。

我们来理解第一句话:

保证被volatile修饰的变量对所有线程都是可见的

如何保证可见性?🤔

被volatile修饰的变量在工作内存修改后会被强制写回主内存,其他线程在使用时也会强制从主内存刷新,这样就保证了一致性。

关于“保证被volatile修饰的变量对所有线程都是可见的”,有种常见的错误理解:

错误理解:由于volatile修饰的变量在各个线程里都是一致的,所以基于volatile变量的运算在多线程并发的情况下是安全的。

这句话的前半部分是对的,后半部分却错了,因此它忘记考虑变量的操作是否具有原子性这一问题。

举个栗子


    private volatile int start = 0;

    private void volatileKeyword() {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    start++;
                }
            }
        };

        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        Log.d(TAG, "start = " + start);
    }

这段代码启动了10个线程,每次10次自增,按道理最终结果应该是100,但是结果并非如此。

为什么会这样?

仔细看一下start++,它其实并非一个原子操作,简单来看,它有两步:

  1. 取出start的值,因为有volatile的修饰,这时候的值是正确的。
  2. 自增,但是自增的时候,别的线程可能已经把start加大了,这种情况下就有可能把较小的start写回主内存中。

所以volatile只能保证可见性,在不符合以下场景下我们依然需要通过加锁来保证原子性:

  • 运算结果并不依赖变量当前的值,或者只有单一线程修改变量的值。(要么结果不依赖当前值,要么操作是原子性的,要么只要一个线程修改变量的值)
  • 变量不需要与其他状态变量共同参与不变约束
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值