volatile关键字的作用详解

1.volatile关键字的作用详解

概要:我们经常在java变量当中发现有些属性用volatile修饰,volatile修饰的属性到底和没用这个关键字修饰的属性有什么不同呢?

1.1 Java内存模型:定义了程序中各种变量的访问规则

​ 首先我们要了解volatile变量的作用,就必须了解Java的内存模型,我们都知道CPU的计算速度是远远大于磁盘io,而应用的瓶颈通常也是消耗在磁盘io,网络io,访问资源等。为了使存储设备更解决cup的处理速度,我们通常会使用高速缓存来作为内存和处理器之间的缓存,提高访问效率。了解过操作系统的也知道操作系统是有很多级的缓存的什么L0,L1这些。其中最快的就是位于cpu附近的寄存器了。

​ 所以Java为了提高性能,也有自身的一个内存缓存模型.如图
在这里插入图片描述

​ 工作内存:java对于cpu各级缓存的抽象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGeduRYD-1618142589157)(C:\Users\penghuazhang\Desktop\valatile.jpg)]

可以看出每个线程有自己的工作内存,线程对与变量的访问都是通过操作自身的工作内存来进行的。后续在通过缓存一致性协议来读取或者写入到主内存当中

2.1.保证内存的可见性:

​ 什么是内存的可见性呢,即在并发情况下,各个线程操作同一个变量看到的都是变量的最新值.我们先来分析一下普通变量的访问流程

比如java线程1修改了一个a变量的值即从a=true改成a=false.这个过程包含线程1修改自身工作内存中a=1的值并写会到主内存中

此时java线程2也访问a变量的值, 正常情况下线程a读取到的因为CPU执行时刷新工作内存的缓存还是挺快的。不过在极端并发情况下

线程2很有可能读取到a=true也就是已经失效的值。线程2只有在线程1写会到主内存, 线程2的缓存才会失效。即线程1修改a的值对于线程2来说不说立即可见的

​ 如何保证:

Java底层定义了一些对于volatile变量属性的访问和修改规则:

​ 对于volatile变量的读取操作都是直接通过主内存来读取的

​ 对于volatile变量的更新操作都是直接操作主内存的

2.2 禁止指令重排序

​ 指令重排序是指:对于一些没用依赖的代码进行编译后的指令重排序,比如int a=1 int b=1;经过编译器的重排后很可能

先执行b=1;

​ 为什么要禁止指令重排序呢?我们来看个例子:

public class Test {

    private static int x = 0, y = 0;
    private static int a = 0, b =0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;) {
            i++;
            x = 0; y = 0;
            a = 0; b = 0;
            CountDownLatch latch = new CountDownLatch(1);

            Thread one = new Thread(() -> {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                }
                a = 1;
                x = b;
            });

            Thread other = new Thread(() -> {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                }
                b = 1;
                y = a;
            });
            one.start();other.start();
            latch.countDown();
            one.join();other.join();

            String result = "第" + i + "次 (" + x + "," + y + ")";
            if(x == 0 && y == 0) {
                System.err.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

你觉得会输出会有哪些情况呢?

在这里插入图片描述

​ 输出01或者10都是正常情况,无非两个线程谁先运行,但是输出00就有问题了,因为线程 1 中的代码,编译器是可以将 a = 1 和 x = b 换一下顺序的,因为它们之间没有数据依赖关系,同理,线程 2 也一样,那就不难得到 x == y == 0 这种结果了。

​ 当用volatile 关键之修饰变量后会在赋值操作过程中形成一个内存屏障,指令重排时不能把后面的指令重排序到内存屏障之前,当然只有一个线程访问内存中的变量的时候是不需要内存屏障的;但是如果两个线程或者更多的线程通过处理器访问同一块内存,且其他的线程在观察另一个,则时候就需要通过内存屏障来保证一致性了。

​ 底层其实就是用volatile 关键字修饰的变量在编译后会多执行一条 “lock addl $0x0,(%esp)”这条指令有两个作用,一个是在它后面的指令不会被重排到它前面。其次它会将本处理器的缓存写到主内存中,并且改写入动作会导致其他cpu中的缓存失效。通过这样一个操作可以让前面volatile 变量的修改对其他处理器立即可见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值