标题 neta 自《计算机网络自顶向下》

思维导图

volatile 在 Java 中被称为轻量级 synchronized。很多并发专家引导用户远离 volatile 变量,因为使用它们要比使用锁更加容易出错。但是如果理解了 volatile 能帮助你写出更好的程序。

  • 当读比写更多时会获得比锁好相当多的性能
  • 比锁更好的伸缩性
  • 比锁使用方便,只需要声明变量即可,代码量小

内存语义

volatile 的讲解

为了方便理解 volatile,用代码来表示一下加了 volatile 的效果。

给变量加上 volatile 相当于在 get 和 set 方法中加了锁。

public synchronized int getX() {return x;
}public synchronized void setX(int x) {this.x = x;
}

注意这里只保证了get 和 set 的原子性,当有其他操作的时候就不是原子性的了。

下面的操作不是原子性的,当个 5 个线程同时执行这个方法 100 次后出现的结果很可能小于 500。

volatile int x;
public void inc() {x++;
}

原因是这个程序相当于

int x;
public synchronized int getX() {return x;
}public synchronized void setX(int x) {this.x = x;
}
public void inc() {int temp = getX(); // 1temp += 1; // 2setX(temp); // 3
}

可以看出即使 get 和 set 操作是原子性的,整个操作也不是原子性的。

当两个线程 A , B 同时执行 inc时,可能会出现

A-1 得到 x = 1

B-1 得到 x = 1

A-2 temp 为 2

B-2 temp 为 2

A-3 x 被设为 2

B-3 x 被设为 2

在执行完毕后 x 的值只增加了 1。

原子性和可见性

我们在 JMM 中讲解 volatile 的内存语义。可以参照着这篇看。JVM内存模型、指令重排、内存屏障概念解析

volatile 保证了新的值能立刻同步到主内存中,以及每次使用前都到主内存刷新。

volatile 通过在写入变量的时候,JVM 会向 CPU 发送一个 lock 前缀指令将变量同步入主内存

而当出现了这个命令以后,所有其他线程上的缓存就会被强制设置成无效,当下次要用到这个变量的时候需要去主内存中取。

通过 Lock 指令

每次使用变量之前都必须先从主内存刷新最新的值。

每次修改变量后都必须立即同步回主内存中,保证其他线程可以看到最新的值。

一个比较有用的抽象:把加了 volatile 的变量当作是没有中间的缓存,所有的数据操作都是在主内存上的。

禁止指令重排

CPU 和编译器为了执行效率,会将指令重排序。如果不知道的可以参照上面那一片博文来对照着读。

volatile 修饰变量不会被指令重排优化,保证代码执行顺序和程序顺序相同。

在几个地方会插入 StoreStoreStoreLoad 阻止重排序。

  • 在 volatile 写前插入 StoreStore 屏障,写后插入 StoreLoad 屏障
  • 在 volatile 读前插入 LoadLoad 屏障,读后插入 LoadStore 屏障

这样的效果就是,在 volatile 写前的操作不能排到它后面。在 volatile 读后的操作不能排到它前面。

如果不知道这两个指令可以看一下上面的博客。 // TODO 马上写完 (咕咕咕

正确的使用 volatile

正确使用 volatile 依赖于

  • 对变量的写操作不依赖于当前值
  • 该变量没有包含在具有其他变量的不变式中

状态标志

也许实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,比如游戏结束,将游戏正在进行的 flag 设置为 false,通知绘图线程停止。

volatile boolean flag = false;
private void waiting() {while(!flag) {// do something}
}

一次性安全发布

一次性安全分布用于双重检查实现单例模式。

private volatile SingleTest instance;
SingleTest getInstance() {if (instance == null) {synchronized (SingleTest.class) {if (instance == null) {instance = new SingleTest();}}}return instance;
}

为什么要用到 volatile 呢?因为新建类分为三步

  • 分配内存空间
  • 初始化对象
  • 设置内存地址,初始化引用

在这里第二步可能重排序,这时候可能会将没有初始化成功就把对象发布出去了,所以需要 volatile 来阻止指令重排。

独立观察

安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。

使用该模式的另一种应用程序就是收集程序的统计信息。清单 4 展示了身份验证机制如何记忆最近一次登录的用户的名字。将反复使用 lastUser 引用来发布值,以供程序的其他部分使用。

该模式是前面模式的扩展;将某个值发布以在程序内的其他地方使用,但是与一次性事件的发布不同,这是一系列独立事件。这个模式要求被发布的值是有效不可变的 —— 即值的状态在发布后不会更改。使用该值的代码需要清楚该值可能随时发生变化。

volatile bean 模式

volatile bean 是线程安全的。在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。

在 volatile bean 模式中,

  • JavaBean 的所有数据成员都是 volatile 类型的
  • 并且 getter 和 setter 方法除了获取或设置相应的属性外,不能包含任何逻辑。、
  • 此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 volatile 时,只有引用而不是数组本身具有 volatile 语义)
@ThreadSafe
public class Person {private volatile String firstName;private volatile String lastName;private volatile int age;public String getFirstName() { return firstName; }public String getLastName() { return lastName; }public int getAge() { return age; }public void setFirstName(String firstName) { this.firstName = firstName;}public void setLastName(String lastName) { this.lastName = lastName;}public void setAge(int age) { this.age = age;}
}

对于 volatile 的优化

在 JDK 7 并发包里新增了一个队列集合 LinkedTransferQueue,它在使用 volatile 变量时,用一种追加字节的方式来优化出队和入队的性能。

它将变量追加到了 64 字节来提高性能。

64 位 CPU 在队列中的元素不足 64 个字节时会将多个元素读入一个缓存行中,在多线程当读取一个元素的时候会锁住这个缓存行,进而导致这个元素附近的元素都不能被读取。

如果一个变量为 64 字节,那么每个元素都被读入不同的缓存中,相邻队列元素就能被不同线程同时访问了。

参考文献

  • 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
  • 方腾飞.Java 并发编程的艺术 [M]. 机械工业出版社, 2015.
  • 正确使用 Volatile 变量
  • JVM内存模型、指令重排、内存屏障概念解析

转载于:https://www.cnblogs.com/zjmeow/p/9787599.html

自顶向下彻底理解 Java 中的 volatile 关键字相关推荐

  1. 如何理解 JAVA 中的 volatile 关键字

    如何理解 JAVA 中的 volatile 关键字 最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂. ...

  2. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  3. 深入理解Java中的final关键字

    深入理解Java中的final关键字 http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什 ...

  4. java中的Volatile关键字使用

    文章目录 什么时候使用volatile Happens-Before java中的Volatile关键字使用 在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是 ...

  5. Java中的Volatile如何工作? Java中的volatile关键字示例

    如何在Java中使用Volatile关键字 在Java采访中,什么是volatile变量以及何时在Java中使用volatile变量是Java 采访中一个著名的多线程采访问题 . 尽管许多程序员都知道 ...

  6. (转)深入理解Java中的final关键字

    转自:http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方 ...

  7. java final 变量 好处_深入理解Java中的final关键字

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

  8. 10074---深入理解Java中的final关键字

    原文 Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有 ...

  9. java中final是啥意思_(转)深入理解Java中的final关键字

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

最新文章

  1. LeetCode35.搜索插入位置
  2. mongodb java连接 集群_java连接mongodb集群
  3. 基于Bootstrap 3.x的免费高级管理控制面板主题:AdminLTE
  4. vim 编辑器的快捷键
  5. 模拟光端机和数字光端机的优缺点介绍
  6. 天池 在线编程 布尔表达式求值(栈)
  7. Anaconda+Win10安装
  8. HTML5缓存和GPS定位
  9. python容量变化类型有哪些_python基础数据类型补充以及编码的进阶
  10. C++ 关于I/O
  11. android开发:Android 中自定义属性(attr.xml,TypedArray)的使用
  12. javaee加密部署,tomcat使用自己的classloader解密
  13. Mapabc——地图标注
  14. python求小于n的最大素数_找出小于n的最大素数,其中n =〜10 ^ 230 - python
  15. 平安京因服务器升级维护什么意思,阴阳师4月27日维护更新公告 堀江由衣猫掌柜降临平安京...
  16. python新版个人所得税代码_python-计算个人所得税(示例代码)
  17. 面试官最容易提出的20个问题
  18. 【C++项目】boost搜索引擎项目
  19. 用python抓取百万网易云热门评论[转载]
  20. ​数字经济指数合集:各省、城市数字经济指数面板数据

热门文章

  1. C语言电话薄登录系统,求助 哈稀表编电话薄程序(c语言) 算法
  2. python快速开发框架_GitHub - lee2029/pyui4win: 一个用python实现业务逻辑、用xml和html/css/js描述界面的windows程序的快速开发框架...
  3. 新颖性搜索(Novelty Search,NS)算法实践——利用NS算法解决迷宫导航问题
  4. linux命令kill_什么是Linux中的kill命令?
  5. seaborn箱线图_Seaborn线图的数据可视化
  6. python参数检查类型_Python类型检查
  7. c# 以太坊代币_C代币
  8. Python sorted()函数
  9. ios部分阴影_iOS UIMenuController UIMenuItem第2部分
  10. Java SE 9:不可变Map和Map.Entry的工厂方法