volatile一张网

volatile一张网

大家好,阿清今天继续和大家聊一下Java针对并发设计的另一个关键字:volatile



01


volatile的特点



回顾一下,并发中的三个问题:操作原子性、缓存可见一致性、指令序重排。

开门见山,volatile能保证缓存可见一致性,并禁止指令重排序。但无法保证操作原子性。

volatile一张网,它放过的就是操作原子性这一个并发问题。

相较于synchronized而言,volatile的同步机制稍弱,但取而代之的是性能的提升。



02


volatile的使用场景



这里引用《Java并发编程实战》一书第31页的内容,

volatile的典型用法:检查某个状态标记以判断是否退出循环


即volatile适合修饰那些状态标记变量。

例如如下代码:

volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}

当一个变量被volatile修饰后,每当对变量进行写操作时,

  1. 会将更改后的最新值立即写入主存中
  2. 会使其他线程中该变量的缓存无效

因此,针对上面的代码,只要有一个线程更改了flag的值,其他的线程中flag对应的缓存就会失效,其他线程在执行时便会主动从主存中读取最新的flag变量。



03


volatile为何不能保证操作的原子性?



引用《Java并发编程:volatile关键字解析[1]》中的🌰:

public class Test {
    public volatile int inc = 0;
     
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
        //保证前面的线程都执行完
        while(Thread.activeCount()>1)  
            Thread.yield();
        System.out.println(test.inc);
    }
}

这段代码在正确执行完后,inc的值应该是10000。

但实际上,在执行完这段代码后,inc的值会比10000小。

因为 自增操作 inc++并不是一个原子操作

inc++的执行过程实际上有3步

  1. 从主存中读取inc变量的值
  2. 对读到的inc的值执行+1操作
  3. 将新的inc的值写入主存中

举🌰说明这个步骤:

假设某一时刻, inc的在内存中的值为10。

  • 线程A在运行到inc++时,执行第一步后,即当读到Inc的值时,便挂起;

  • 而此时线程B在运行到inc++时,成功执行了上述3步,即现在主存中的值为11。

  • 而当线程A重新唤醒时,它已经读到了一个inc值为10,这时它将继续执行+1操作,并将11再次写入主存。

  • 在经历两次+1操作后,inc的值却仍然是11,这说明volatile不能保证操作原子性。



04


缓存可见一致性



这个时候,可能会有小伙伴来问,前面提过,若对volatile修饰的变量进行写操作,会使其他的线程中该变量的缓存失效呀,那为啥线程A在重新唤醒时,不能发现缓存已经失效呢?

阿清的理解是:缓存确实已经失效,但需要去读这个缓存时,才能发现。由于线程A在之前已经获取到了inc的值,它便再没有理会inc的缓存,直接拿着之前的inc的值,进行了接下来的操作,由此发生了错误。

因此,若某个变量是一个精确的值,且它最新的值会受到它之前的值的影响时,此时这个变量是不适合用volatile来修饰的

说来说去,若针对某一个变量的操作不是原子操作,这个时候最好不要使用volatile来修饰它。

另外,volatile在单例模式的 DoubleCheck模式中也有使用,朋友们可以留心下 六只栗子 关于单例模式的推文呀~



05


总结



今天的内容并不多,主要向大家介绍了volatile关键字的特点。

总之,一旦某个变量被volatile修饰:

  1. 就能保证多个线程在自己的工作内存中对这个变量的访问可见一致性。

  2. 禁止对该变量进行指令序重排(某变量被volatile修饰后,不能改变该变量以上、以下语句的执行顺序)。

volatile对并发问题的保证:

  1. 能保证可见一致性、指令有序性。

  2. 不能保证操作原子性

关注六只栗子,面试不迷路。

参考资料

[1]

Java并发编程:volatile关键字解析: https://www.cnblogs.com/dolphin0520/p/3920373.html




作者    阿清

编辑   一口栗子  




volatile一张网

volatile一张网

volatile一张网

volatile一张网



原文始发于微信公众号(六只栗子):volatile一张网

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88545.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!