Java关键字Volatile

java关键字Volatile用于将java变量标记为存储在主内存中,这就意味着每次读取Volatile修饰的变量时都是从计算机的主内存中读取,而不是从CPU的缓存中读取,并且每次对Volatile变量写的时候都将写入主内存,而不仅仅是CPU缓存

可见性问题

Java关键字Volatile保证可以跨线程查看变量的变化,下面详细来说一下这个问题
在线程操作非Volatile变量的多线程应用程序中,出于性能原因,每个线程可以在处理它们时将变量从主内存复制到CPU高速缓存中。如果您的计算机包含多个CPU,则每个线程可以在不同的CPU上运行。这意味着,每个线程可以将变量复制到不同CPU的CPU缓存中。这在这里说明:



对于非Volatile变量,java虚拟机从主内存读取到CPU缓存,或从CPU缓存读取到主内存,这可能导致一系列问题:
假设两个线程或者多个线程访问一个共享对象,共享对象包含一个counter变量,像这样:

public class SharedObject {
    public int counter = 0;
}

假设只有线程1递增counter变量,但线程1和线程2都可能时不时读取counter变量。
如果counter变量没有声明为Volatile,则无法保证counter从CPU缓存读取到主内存中的具体时间,这就意味着CPU缓存中的变量值可能跟主内存中变量值不同,这种情况如下所示:



因为还没有被线程写入主线程,而导致另一个线程没有看到变量的最新值得问题,被称为线程的“可见性”问题
,线程的更新操作对其他线程不可见

Java Volatile可见性保证

java Volatile关键帧意在解决线程的可见性问题,通过对counter声明volatile,对象counter所有写的操作将立即写入主内存,同时对counter的读操作也是直接访问主内存

public class SharedObject {
    public volatile int counter = 0;
}

因此声明一个Volatile,可以保证对其他线程的可见性
在上面给出的场景中,一个线程(T1)修改计数器,另一个线程(T2)读取计数器(但从不修改它),声明了volatile的counter足以保证T2对counter变量写入的可见性。
但是,如果T1和T2都在增加counter变量,那么 counter变量声明volatile就不够了。稍后会详细介绍。

volatile完全可见性保证

实际上,volatile的可见性保证超出了volatile变量本身,可见性保证如下:
1.如果线程A 对volatile变量进行写操作,那么线程B可以立刻读取相同的volatile变量,在对volatile变量写前,所有的变量对线程A都是可见的。在读取volatile变量后对线程B同样是可见的。
2.如果线程A读取volatile变量,则读取变量时线程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;
    }
}

改update写入三个变量,只有days用了volatile
volatile完全可见性意味着,当对days进行写入时,线程所有的可见变量都会写入主内存(不仅仅是volatile变量自己写入到主存中,其他被该线程修改的所有变量也会刷新到主存),这就意味着,当对days进行写入时,years和months也将写入主内存,
当对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;
    }

注意totalDays从读取days的值开始到获取total,当读取days时,years和months也会读取到主内存中,因此可以保证读取到days、years和months的最新值

指令重排

只要指令的语义含义不变,jvm和CPU就可以出于性能的原因,重新排序指令的程序,看以下说明:

int a = 1;
int b = 2;
a++;
b++;

这些指令可以按一下方式重排,而不会丢失程序的语义含义:

int a = 1;
a++;

int b = 2;
b++;

然而,当程序中有一个volatile字段时,指令重新排序提出了挑战。让我们从MyClass类中看看一下java 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;
    }
}

一旦update方法写入一个days值,years和months也会被写入主内存中,但是如果jvm把指令重排序了

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

当days值被修改时,months和years仍然被写入主内存,但是这一次days的值的改变在months和years写入之前,因此新值没法正确的对其他线程可见,指令重排的语义以前被改变

Java volatile Happens-Before 保证

happens-before 关系是程序语句之间的排序保证,这能确保任何内存的写,对其他语句都是可见的。
为了解决指令重排挑战,volatile除了可见性保证之外,Java 关键字还提供“Happens-Before Guarantee”规则。"Happens-Before"保证:
如果线程A写入一个volatile变量,随后线程B读取相同的变量。那么变量对线程A来说在写入变量前就是可见的,对于B来说读取完变量后,对该变量也是可见的。
volatile变量的读和写指令不能由JVM重新排序()。读写指令前后可以重排序,但是volatile读和写不能与这些指令混合。无论什么指令都应该在volatile变量读写之后。

volatile并不足够解决所有问题

volatile虽然能满足直接把数据写入主内存并且直接从主内存中取出,仍然存在不足的情况
在前面解释的情况中,只有线程1写入共享counter变量,声明该counter变量volatile足以确保线程2始终看到最新的写入值。
实际上,如果写入volatile变量的新值不依赖于其先前的值,则多个线程甚至可以写入共享变量,并且仍然具有存储在主存储器中的正确值。换句话说,如果将值写入共享volatile变量的线程首先不需要读取其值来计算其下一个值。
一旦线程需要首先读取volatile变量的值,并且基于该值为共享volatile变量生成新值,volatile变量就不再足以保证正确的可见性。读取volatile 变量和写入新值之间的短时间间隔会产生竞争条件 ,其中多个线程可能读取volatile变量的相同值,为变量生成新值,并在将值写回时主存 - 覆盖彼此的值。
多个线程递增相同计数器的情况恰好是 volatile变量不够的情况。以下部分更详细地解释了这种情况。

想象一下,如果线程1将counter值为0 的共享变量读入其CPU高速缓存,则将其增加到1并且不将更改的值写回主存储器。然后,线程2可以counter从主存储器读取相同的变量,其中变量的值仍为0,进入其自己的CPU高速缓存。然后,线程2也可以将计数器递增到1,也不将其写回主存储器。这种情况如下图所示:


线程1和线程2现在几乎不同步。共享counter变量的实际值应为2,但每个线程的CPU缓存中的变量值为1,而主存中的值仍为0.这是一个混乱!即使线程最终将共享counter变量的值写回主存储器,该值也将是错误的。

volatile在什么时候使用

正如我前面提到的,如果两个线程都在读取和写入共享变量,那么使用 volatile关键字是不够的。 在这种情况下,您需要使用synchronized来保证变量的读取和写入是原子性。读取或写入volatile变量不会阻止线程读取或写入。为此,您必须在关键部分周围使用synchronized 关键字。

作为synchronized块的替代方法,您还可以使用java.util.concurrent包中找到的众多原子数据类型之一。例如,AtomicLong或者 AtomicReference其他更多。

如果只有一个线程读取和写入volatile变量的值,而其他线程只读取变量,那么读取线程将保证看到写入volatile变量的最新值。则可以使用volatile关键词

volatile关键字适用于32位和64位变量。

volatile的性能因素

volatile变量会导致变量读取和写入主内存。读取和写入主内存比访问CPU缓存更昂贵。访问volatile变量也会阻止指令重新排序,这是一种正常的性能增强技术。因此,在真正需要强制实施变量可见性时,应该只使用volatile变量。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容