闲碎多线程3星

Java并发 --- volatile关键字

2021-06-19  本文已影响0人  _code_x

写在前

在并发编程中,最需要处理的就是线程之间的通信和线程间的同步问题,JMM中可见性、原子性、有序性也是这两个问题带来的。volatile 是java虚拟机提供的轻量级的同步机制

在并发编程中,需要解决的两个问题:

通信:在命令式编程中,线程之间的通信包括共享内存和消息传递 而 java并发采用的是共享内存模型,线程之间共享程序的公共状态,通过读写内存总的公共状态来隐式通信

JMM关于同步的规定:

1.线程解锁前,必须把共享变量的值刷回主内存
2.线程加锁前,必须读取共享内存的最新值到自己的本地内存
3.加锁解锁是同一把锁

volatile关键字主要作用

保证内存可见性

为什么 volatile 关键字可以有可见性?

volatile是如何保证有序性呢?答案是内存屏障Memory Barrier

Memory Barrier 可以保证内存可见性和特定操作的执行顺序

volatile写操作之后都会插入一个store屏障,将工作内存中的值刷回到主内存,在读操作之前都会插入一个load屏障,从主内存读取最新的数据(可见性),而无论是stroe还是load都会告诉编译器和cpu,屏障前后的指令都不要进行重排序优化(禁止指令重排)

原子性:在 Java 中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量)才是原子操作。(变量之间的相互赋值不是原子操作,比如 y = x,实际上是先读取 x 的值,再把读取到的值赋值给 y 写入工作内存)

禁止指令重排

什么是数据依赖性?

对同一数据的两个操作中只要有一个是写操作,那么就存在数据依赖性,比如写后写,读后写,写后读。

比如下面的代码:

boolean contextReady = false;
//在线程A中执行:
context = loadContext();    // 步骤 1
contextReady = true;        // 步骤 2

//在线程B中执行:
while(!contextReady ){ 
   sleep(200);
}
doAfterContextReady (context);

以上程序看似没有问题。线程 B 循环等待上下文 context 的加载,一旦 context 加载完成,contextReady == true 的时候,才执行 doAfterContextReady 方法。

但是,如果线程 A 执行的代码发生了指令重排,也就是上面的步骤 1 和步骤 2 调换了顺序,那线程 B 就会直接跳出循环,直接执行 doAfterContextReady() 方法导致出错。而 volatile 采用「内存屏障」这样的 CPU 指令就解决这个问题,不让它指令重排。

使用 volatile 变量进行写操作,汇编指令带有 lock 前缀,相当于一个内存屏障,后面的指令不能重排到内存屏障之前。使用 lock 前缀引发两件事:① 将当前处理器缓存行的数据写回系统内存。②使其他处理器的缓存无效。相当于对缓存变量做了一次 store 和 write 操作,让 volatile 变量的修改对其他处理器立即可见。

使用场景

从上面的总结来看,我们非常容易得出 volatile 的使用场景:

  1. 运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
  2. 变量不需要与其他的状态变量共同参与不变约束。

比如下面的场景,就很适合使用 volatile 来控制并发,当 shutdown() 方法调用的时候,就能保证所有线程中执行的 work() 立即停下来。

volatile boolean shutdownRequest;
private void shutdown(){
    shutdownRequest = true;
}
private void work(){
    while (!shutdownRequest){
        // do something
    }
}

总结与补充

可以创建Volatile数组吗?

volatile能使得一个非原子操作变成原子操作吗?

虽然volatile只能保证可见性不能保证原子性,但用volatile修饰long和double可以保证其操作原子性。

volatile和synchronized的区别与联系

volatile可以保证线程安全?

单纯使用 volatile 关键字是不能保证线程安全的!线程安全必须保证原子性,可见性,有序性。而volatile只能保证可见性和有序性!

volatile底层的实现机制?

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

拓展:缓存一致性?

巨人的肩膀

https://blog.csdn.net/yuyecsdn/article/details/103454244
https://www.jianshu.com/p/7ca933a716a9
https://www.jianshu.com/p/be5e7c355d78
https://www.javanav.com/interview/534e046986274288b71684704cb68162.html
https://blog.csdn.net/qq_33330687/article/details/80990729

上一篇下一篇

猜你喜欢

热点阅读