资讯专栏INFORMATION COLUMN

理解java Volatile 关键字

ConardLi / 1392人阅读

摘要:最近在看多线程相关,看到这篇来自大神关于关键字的讲解感觉非常详细易懂,特此转载一下。如果对增加声明则所有线程对的写都会立即刷新到主存中,而且所有对的读也都直接从主存中去读。

最近在看java多线程相关,看到这篇来自大神Jakob Jenkov关于Volatile关键字的讲解感觉非常详细易懂,特此转载一下。
原文链接:http://tutorials.jenkov.com/j...

内存可见性问题

在多线程应用中,对于每个非Volatile变量,每个cpu会从内存中拷贝一份副本到cpu的高速缓存中。
假设一种场景,两个或两个以上的线程去访问共享对象SharedObject

public class SharedObject {

    public int counter = 0;

}

线程1去写counter值,线程1和线程2会不定期的读counter的值,则有可能存在两个cpu中缓存的counter值不一样,cpu1写入到缓存中的counter值没有刷新到主存,如下图的情况:

这就叫可见性问题。

在java中,Volatile关键字可以保证变量的可见性。

如果对counter增加Volatile声明:

public class SharedObject {

    public volatile int counter = 0;

}

则所有线程对counter的写都会立即刷新到主存中,而且所有对counter的读也都直接从主存中去读。

Volatile关键字的可见性保证不仅限于被Volatile声明的变量本身

还有以下两种变量即使没有用volatile声明,也可以得到相应的可见性保证

如果线程A去写一个volatile变量,随后线程B去读这个volatile变量,则所有在线程A写这个volatile变量之前对线程A可见的变量,在线程B读这个volatile变量之后也对线程B可见。

如果线程A去读一个volatile变量,则所有对线程A可见的变量,在线程A读取这个volatile变量时,会从主存中重新读一次

晦涩难懂,举个栗子:

解释第一条:

public class MyClass {
    private int years;
    private int months
    private volatile int days;


    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

当days变量被写入时,years和months因为对当前线程可见,也会被写入到主存中。

解释第二条:

ublic 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;
    }
}

totalDays()方法执行时,第一步读取days的值,因为days时volatile变量,所以所有对当前线程可见的变量都会从主存中重新读一次。

但是这样对带来一个由重排序而产生的新问题

为了提高性能,CPU和JVM都会指令在不影响语义的前提下进行重排序操作,举个栗子:

上面的update方法可能会被重排序成:(为了方便理解,将参数名加了个new后缀)

public class MyClass {
    private int years;
    private int months
    private volatile int days;


   public void update(int years, int monthsNew, int daysNew){
    this.days   = days;
    this.months = monthsNew;
    this.years  = daysNew;
    }
}

这种情况下虽然在days被写入的时候,years和months也会被刷新到主存中,但是并不是monthsNew和daysNew的值,这就意味着,这种重排序改变了语义。

重排序问题的解决方案:volatile的 Happens-Before 原则

为解决这个问题,java赋予了volatile变量一些 Happens-Before 原则的保证。
原则如下:

对其它变量的读和写操作不能被重排序到对一个volatile变量的写操作之后。

对其他变量的读和写操作不能被重排序到对一个volatile变量的读操作之前。

注意:两条原则反过来是不保证的,比如对其它变量的读和写操作有可能会被重排序到对一个volatile变量的写操作之前。

未完待续~~~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69563.html

相关文章

  • 理解Java中的Volatile键字(demo)

    摘要:现在的处理器,一般是会有多个的,每个线程可能运行在不同的,那么线程修改完成的值,是首先保存在中去。考虑这样一种情况现在有两个线程,线程和线程,他们不时会去读取这个共享变量。 什么是volatile 关键字volatile 提供了Java 虚拟机中最轻量级的同步机制。在meidium 中有篇文章说:Volatile specifier is used to indicate that a...

    codecraft 评论0 收藏0
  • 死磕 java同步系列之volatile解析

    摘要:前半句是指线程内表现为串行的语义,后半句是指指令重排序现象和工作内存和主内存同步延迟现象。关于内存模型的讲解请参考死磕同步系列之。目前国内市面上的关于内存屏障的讲解基本不会超过这三篇文章,包括相关书籍中的介绍。问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile...

    番茄西红柿 评论0 收藏0
  • 死磕 java同步系列之volatile解析

    摘要:前半句是指线程内表现为串行的语义,后半句是指指令重排序现象和工作内存和主内存同步延迟现象。关于内存模型的讲解请参考死磕同步系列之。目前国内市面上的关于内存屏障的讲解基本不会超过这三篇文章,包括相关书籍中的介绍。问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile...

    番茄西红柿 评论0 收藏0
  • 死磕 java同步系列之volatile解析

    摘要:前半句是指线程内表现为串行的语义,后半句是指指令重排序现象和工作内存和主内存同步延迟现象。关于内存模型的讲解请参考死磕同步系列之。目前国内市面上的关于内存屏障的讲解基本不会超过这三篇文章,包括相关书籍中的介绍。问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile...

    kviccn 评论0 收藏0
  • 掌握Java的内存模型,你就是解决并发问题最靓的仔

    摘要:掌握的内存模型,你就是解决并发问题最靓的仔编译优化说的具体一些,这些方法包括和关键字,以及内存模型中的规则。掌握的内存模型,你就是解决并发问题最靓的仔共享变量蓝色的虚线箭头代表禁用了缓存,黑色的实线箭头代表直接从主内存中读写数据。 摘要:如果编写的并发程序出现问题时,很难通过调试来解决相应的问题,此时,需要一行行的检查代码...

    番茄西红柿 评论0 收藏2637

发表评论

0条评论

最新活动
阅读需要支付1元查看
<