Java volatile 整理

一、volatile 特性

多线程环境下,每个线程都有自己的一个工作内存,对于共享变量,读时是先从主内存加载到工作内存,写时是从工作内存写回主内存。

这个工作内存就相当于一个线程级别的缓存,与其他缓存一样,存在一致性问题,比如一个共享变量的值在一个线程的工作内存中被修改了,那么什么时候同步回主内存呢?这个是没有明确规定的,这就导致了问题,例如多个线程都在自己的工作内存中修改共享变量的值,而主内存中的值是旧的,各个线程也不知道其他线程对变量的改动,这就是不可见问题。

volatile可以保证:

  • 每次读取volatile变量时都是从主内存中读的。
  • 每次写volatile变量时都会写回主内存。

从而,volatile 解决了可见性问题,每次读写都是直接关联主内存的,这是 volatile 的首个重要特性。还有一个重要特性是防止指令重排序,具体的后面再说。

小结一下volatile的特性:

  1. 保证了共享变量在多线程下的可见性。
  2. 防止指令重排序。

二、volatile 是怎么保证可见性的?

1. 什么是可见性问题?

多线程应用中,为了性能,每个线程都会把共享变量从主内存拷贝到自己的工作内存中,多cpu计算机中每个线程运行在不同的cpu中,每个线程就把共享变量拷贝到自己的cpu cache中。

image

对于普通变量,JVM什么时候将其从主内存读到 cpu cache 中?以及什么时候从 cpu cache 写回主内存?这些都是没有保证的。

public class SharedObject {

    public int counter = 0;

}

如这段代码,假设线程1对counter执行增加操作,线程1和线程2都会时不时的读取 counter,线程1的操作结果什么时候写回主内存是没谱的,就会发生下图中的情况:

image

可见性问题:一个线程修改了变量值,由于还没有写回主内存,导致其他线程看不到最新值,也就是一个线程的更新对其他线程不可见。

2. volatile 对可见性的保证

变量声明了 volatile 之后,所有对其写后都会立即写回主内存,所有对其的读操作都会直接从主内存中读。

大概原理:

多处理器下,会实现一个缓存一致性协议,每个处理器通过分析在总线上传播的数据来检查自己缓存的值是否过期了,所以当 volatile 变量值写回到主内存后,其他处理器会发现自己缓存行对应的内存地址被修改了,就会将当前缓存置为无效状态,所以当再次操作这个变量时就得从主内存重新读取到缓存,从而拿到最新值。

3. 全可见性保证

事实上,volatile 对可加性的保证已经超过了 volatile 变量本身,还遵循如下原则:

  • 如果线程A对一个volatile变量进行写入,并且线程B接下来对这个变量进行读取,那么在写volatile变量前对线程A可见的所有变量,在线程B读取volatile变量之后这些变量对线程B也是可见的。
  • 如果线程A读取了一个volatile变量,在读取之后,线程A中所有可见变量都会从主内存重新读取。

示例代码:

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

udpate()写了3个变量,其中只有 daysvolatile

days写入后,所有可见变量(years、months)也会写入主内存。

读取的示例代码:

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时,years、months也会从主内存中读取。

三、volatile 是怎么防止重排序的?

为了提高性能,JVM和CPU允许对指令的顺序进行重排,只要保证整体语义和结果是正确的。

例如:

int a = 1;
int b = 2;

a++;
b++;

可以重拍为:

int a = 1;
a++;

int b = 2;
b++;

看下面的代码:

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也都会写入主内存。但如果发生了重拍的话呢,例如:

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

days的写入被排在了第一行,虽然写入的同时也会把years months的值写入主内存,但之后又修改了years months的值,这时他俩的最新值就不会被马上写入主内存了,这就与重排序之前的代码意义不同了。

为了解决重排序问题,volatile 给出了“happens-before”保证:

  • 对于其他变量的读写,如果本来是在写volatile之前,那么不能被重排到之后。volatile变量写之前的读写操作可以保证发生在写之前。注意,对于本来是在写volatile变量之后的其他变量读写操作是可以被重排到写volatile变量之前的。把后面的重排到前面可以,但把前面的排到后面是不允许的。
  • 对于其他变量的读,如果本来是发生在读volatile之后的,那么不能被重排到之前。注意,对于本来发生在读volatile之前的其他变量的读可以重拍到之后。把前面的读放到后面可以,但把后面的放到前面不行。

参考:http://tutorials.jenkov.com/java-concurrency/volatile.html

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

推荐阅读更多精彩内容