JVM-volatile的内存语义

本篇内容主要摘自《Java并发编程的艺术-方腾飞》

  • 更多相关文章见笔者博客

1. volatile特性

  • 理解 volatile 特性的一个好方法是把对 volatile 变量的单个读 / 写,看成是使用同一个锁对这些单个读 / 写操作做了同步。下面通过具体的示例来说明,示例代码如下
class VolatileFeaturesExample {
    volatile long vl = 0L; //使用volatile声明64位的long型变量
 
    public void set(long l) {
        vl = l; //单个volatile变量的写
    }
 
    public void getAndIncrement() {
        vl++; //复合(多个)volatile变量的读/写
    }
 
    public long get() {
        return vl; //单个volatile变量的读
    }
}
  • 假设有多个线程分别调用上面程序的 3 个方法,这个程序在语义上和下面程序等价
class VolatileFeaturesExample {
    long vl = 0L; // 64 位的 long 型普通变量
    public synchronized void set(long l) { // 对单个的普通变量的写用
        vl = l;
    }
 
    public void getAndIncrement () {// 普通方法调用
        long temp = get();// 调用已同步的读方法
        temp += 1L;// 普通写操作
        set(temp);// 调用已同步的写方法
    }
 
    public synchronized long get() { // 对单个的普通变量的读用同一个锁同步
        return vl;
    }
}
  • 如上面示例程序所示,一个 volatile 变量的单个读 / 写操作,与一个普通变量的读 / 写操作都是使用同一个锁来同步,它们之间的执行效果相同
  • 锁的 happens-before 规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入
  • 锁的语义决定了临界区代码的执行具有原子性。这意味着,即使是 64 位的 long 型和 double 型变量,只要它是 volatile 变量,对该变量的读 / 写就具有原子性。如果是多个volatile 操作或类似于volatile++ 这种复合操作,这些操作整体上不具有原子性
  • 简而言之,volatile 变量自身具有下列特性
    • 可见性 对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后
      的写入
    • 原子性 对任意单个 volatile 变量的读 / 写具有原子性,但类似于 volatile++ 这种复合操作不具有原子性

2. volatile 写 - 读建立的 happens-before 关系

  • 上面讲的是 volatile 变量自身的特性,对程序员来说,volatile 对线程的内存可见性的影响比 volatile 自身的特性更为重要,也更需要我们去关注

  • 从 JSR-133 开始(即从 JDK5 开始),volatile 变量的写 - 读可以实现线程之间的通信。

  • 从内存语义的角度来说,volatile 的写 - 读与锁的释放 - 获取有相同的内存效果:volatile
    写和锁的释放有相同的内存语义;volatile 读与锁的获取有相同的内存语义

  • 请看下面使用 volatile 变量的示例代码

    class VolatileExample {
        int              a    = 0;
        volatile boolean flag = false;
     
        public void writer() {
            a = 1; //1
            flag = true; //2
        }
     
        public void reader() {
            if (flag) { //3
                int i = a; //4
                //……
            }
        }
    }
     
    

    假设线程 A 执行 writer() 方法之后,线程 B 执行 reader() 方法。根据 happens-before 规
    则,这个过程建立的 happens-before 关系可以分为 3 类:
    1)根据程序次序规则,1 happens-before 2; 3 happens-before 4
    2)根据 volatile 规则,2 happens-before 3
    3)根据 happens-before 的传递性规则,1 happens-before 4

    上述 happens-before 关系的图形化表现形式如下

image

在上图中,每一个箭头链接的两个节点,代表了一个 happens-before 关系。黑色箭头表示程序顺序规则;橙色箭头表示 volatile 规则;蓝色箭头表示组合这些规则后提供的 happens-before保证

这里 A 线程写一个 volatile 变量后,B 线程读同一个 volatile 变量。A 线程在写 volatile 变量之前所有可见的共享变量,在 B 线程读同一个 volatile 变量后,将立即变得对 B 线程可见。


3. volatile 写 - 读的内存语义

volatile 写的内存语义如下

  • 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。

以上面示例程序 VolatileExample 为例,假设线程 A 首先执行 writer() 方法,随后线程 B执行 reader() 方法,初始时两个线程的本地内存中的 f lag 和 a 都是初始状态。图 3-17 是线程A 执行 volatile 写后,共享变量的状态示意图

image

如上图所示,线程 A 在写 f lag 变量后,本地内存 A 中被线程 A 更新过的两个共享变量的值被刷新到主内存中。此时,本地内存 A 和主内存中的共享变量的值是一致的

  • volatile 读的内存语义如下

    当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量

图 3-18 为线程 B 读同一个 volatile 变量后,共享变量的状态示意图。

如图所示,在读 f lag 变量后,本地内存 B 包含的值已经被置为无效。此时,线程 B 必须从主内存中读取共享变量。线程 B 的读取操作将导致本地内存 B 与主内存中的共享变量的值变成一致

如果我们把 volatile 写和 volatile 读两个步骤综合起来看的话,在读线程 B 读一个volatile 变量后,写线程 A 在写这个 volatile 变量之前所有可见的共享变量的值都将立即变得对读线程 B 可见

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

推荐阅读更多精彩内容