详细分析java并发之volatile关键字

 更新时间:2020年06月24日 10:41:52   作者:onlythinking  
这篇文章主要介绍了java并发之volatile关键字的的相关资料,文中代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下

Java面试中经常会涉及关于volatile的问题。本文梳理下volatile关键知识点。

volatile字意为“易失性”,在Java中用做修饰对象变量。它不是Java特有,在C,C++,C#等编程语言也存在,只是在其它编程语言中使用有所差异,但总体语义一致。比如使用volatile 能阻止编译器对变量的读写优化。简单说,如果一个变量被修饰为volatile,相当于告诉系统说我容易变化,编译器你不要随便优化(重排序,缓存)我。

Happens-before

规范上,Java内存模型遵行happens-before。

volatile变量在多线程中,写线程和读线程具有happens-before关系。也就是写值的线程要在读取线程之前,并且读线程能完全看见写线程的相关变量。

happens-before:如果两个有两个动作AB,A发生在B之前,那么A的顺序应该在B前面并且A的操作对B完全可见。

happens-before 具有传递性,如果A发生在B之前,而B发生在C之前,那么A发生在C之前。

如何保证可见性

多线程环境下counter变量的更新过程。线程1先从主存拷贝副本到CPU缓存,然后CPU执行counter=7,修改完后写入CPU缓存,等待时机同步到主存。在线程1同步主存前,线程2读到counter值依然为0。此时已经发生内存一致性错误(对于相同的共享数据,多线程读到视图不一致)。因为线程2看不见线程1操作结果,也将这个问题称为可见性问题。

public class SharedObject {
  public int counter = 0;
}

因为多了缓存优化导致,导致可见性问题。所以volatile通过消除缓存(描述可能不太准确)来避免。例如当使用volatile修饰变量后,操作该变量读写直接与主存交互,跳过缓存层,保证其它读线程每次获取的都是最新值。

public volatile int counter = 0;

volatile 不单只消除修饰的变量的缓存。事实上与之相关的变量在读写时也会消除缓存,如同使用了volatile一样。

如下 years,months,days 三个变量中只有days是volatile,但是对years,months读写操作也和days时也会跳过缓存,其它线程每次读到的都是最新值。

public class MyClass {
  private int years;
  private int months
  private volatile int days;
  public int totalDays() {
    int total = this.days;
    total += months * 30;
    total += years * 365;
    return total;
  }
  public void update(int years, int months, int days){
    this.years = years;
    this.months = months;
    this.days  = days;
  }
}

这是为什么?我们分析一下。

一个写线程调用 update,读线程调用totalDays。单线程中,对于update方法,wa与wb存在happens-before关系, wa在 wb 之前执行并对wb可见。

多线程中rc与wb存在happens-before关系,wb在rc之前执行并对rc可见。根据 happens-before传递性,wa需要在rc前先执行并对rc可见。

因为wb是volatile变量,所以rc获取的years,months也是最新值。

 

我们知道出于性能原因,JVM和CPU会对程序中的指令进行重新排序。如果update方法里面wa和wb顺序被重排,那它们的happens-before关系将不在成立。

为了避免这个问题,volatile对重排序做了保证 对于发生在volatile变量操作前的其他变量的操作不能重新排序。

由此我们得到volatile通过消除缓存和防止重排保证线程的可见性。

volatile保证线程安全?

讨论线程安全,大家都会提及原子性,顺序性,可见性。volatile侧重于保证可见性,也就是当写的线程更新后,读线程总能获得最新值。在只有一个线程写,多个线程读的场景下,volatile能满足线程安全。可如果多个线程同时写入volatile变量时,则需要引入同步语义才能保证线程安全。

模拟10个线程同时写入volatile变量,一个线程读counter,执行完后正确结果应该是counter=10。

  public static class WriterTask implements Runnable {
    private final ShareObject share;
    private final CountDownLatch countDownLatch;
    public WriterTask(ShareObject share, CountDownLatch countDownLatch) {
      this.share = share;
      this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
      countDownLatch.countDown();
      share.increase();
    }
  }
  
  public class ShareObject {
    private volatile int counter;
    public void increase() {
      this.counter++;
    }
  }

执行结果出现counter=5或6 错误结果。

通过 synchronized,Lock或AtomicInteger 原子变量保证了结果的正确。

完整demo https://gist.github.com/onlythinking/ba7ca7aa5faf00a58f4cedae474fa6f6

volatile性能

volatile变量带来可见性的保证,访问volatile变量还防止了指令重排序。不过这一切是以牺牲优化(消除缓存,直接操作主存开销增加)为代价,所以不应该滥用volatile,仅在确实需要增强变量可见性的时候使用。

总结

本文记录了volatile变量通过消除缓存,防止指令重排序来保证线程可见性,并且在多线程写入的变量的场景下,不保证线程安全。

欢迎大家留言交流,一起学习分享!!!

以上就是详细分析java并发之volatile关键字的详细内容,更多关于JAVA volatile关键字的资料请关注脚本之家其它相关文章!

相关文章

  • idea中acitviti使用acitBPM插件出现乱码问题及解决方法

    idea中acitviti使用acitBPM插件出现乱码问题及解决方法

    这篇文章主要介绍了idea中acitviti使用acitBPM插件出现乱码问题及解决方法,通过将File Encodings内容设置为UTF-8,本文通过图文展示,需要的朋友可以参考下
    2021-06-06
  • 基于Redisson实现注解式分布式锁的示例代码

    基于Redisson实现注解式分布式锁的示例代码

    这篇文章主要为大家详细介绍了如何基于Redisson实现注解式分布式锁,文中的示例代码讲解详细,具有一定的参考价值,需要的可以了解一下
    2023-07-07
  • Springboot 内部服务调用方式

    Springboot 内部服务调用方式

    这篇文章主要介绍了Springboot 内部服务调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Spring Security和Shiro的相同点与不同点整理

    Spring Security和Shiro的相同点与不同点整理

    在本篇文章里小编给大家整理的是关于Spring Security和Shiro的相同不同点整理,需要的朋友们可以参考下。
    2020-02-02
  • Spring框架设值注入操作实战案例分析

    Spring框架设值注入操作实战案例分析

    这篇文章主要介绍了Spring框架设值注入操作,结合具体实例形式分析了spring框架设值注入相关实现与使用方法,需要的朋友可以参考下
    2019-11-11
  • 查看jdk(java开发工具包)安装路径的两种方法

    查看jdk(java开发工具包)安装路径的两种方法

    若已经安装好了jdk(java开发工具包),也配置了环境变量,事后却忘了安装路径在哪,如何查看jdk安装路径?本文给大家介绍了两种查看jdk(java开发工具包)安装路径的方法,需要的朋友可以参考下
    2023-12-12
  • 实例详解Java调用第三方接口方法

    实例详解Java调用第三方接口方法

    很多项目都会封装规定好本身项目的接口规范,所以大多数需要去调用对方提供的接口或第三方接口(短信、天气等),下面这篇文章主要给大家介绍了关于Java调用第三方接口方法的相关资料,需要的朋友可以参考下
    2022-06-06
  • 深入浅出讲解Java比较器及数学常用类

    深入浅出讲解Java比较器及数学常用类

    这篇文章主要介绍了深入浅出讲解Java比较器及数学常用类,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • Maven Repository 使用方法

    Maven Repository 使用方法

    对于Java开发者来说,Maven Repository是个必须掌握的网站,它可以让开发者更加方便地管理和维护 Java 项目的依赖项,同时简化了项目开发的过程,这篇文章主要介绍了Maven Repository 使用方法,需要的朋友可以参考下
    2024-02-02
  • JVM入门之JVM内存结构内容详解

    JVM入门之JVM内存结构内容详解

    这篇文章主要介绍了JVM入门之JVM内存结构内容详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09

最新评论