volatile

保证可见性

  • 一个线程修改volatile变量的值时,该变量的新值会立即刷新到主内存中,这个新值对其他线程来说是立即可见的
  • 一个线程读取volatile变量的值时,该变量在本地内存中缓存无效,需要到主内存中读取
boolean stop = false;// 是否中断线程1标志
//Tread1
new Thread() {
public void run() {
while(!stop) {
doSomething();
}
};
}.start();
//Tread2
new Thread() {
public void run() {
stop = true;
};
}.start();

保证有序性

graph LR
任意操作-->|不能重排序|volatile写
volatile读-->|不能重排序|任意操作
volatile写-->|不能重排序|a[volatile读]

volatile写实际上是在前面加了一个StoreStore屏障,要求不能和前面的写操作进行重排序。读操作没有要求,只是因为volatile读后面加了LoadStore屏障,因此也不能和前面的volatile读重排序。至于普通读,应该是可以进行重排的,猜测是由于读取对数据本身不产生影响,多线程情况下不存在安全问题(很多资料中都写的是前面任意操作都不能进行重排序)

boolean inited = false;// 初始化完成标志
//线程1:初始化完成,设置inited=true
new Thread() {
public void run() {
context = loadContext(); // 语句1
inited = true; // 语句2
};
}.start();
//线程2:每隔1s检查是否完成初始化,初始化完成之后执行doSomething方法
new Thread() {
public void run() {
while(!inited){ // 语句3
Thread.sleep(1000);
}
doSomething(context);
};
}.start();

线程1中,语句1和语句2之间不存在数据依赖关系,JMM允许这种重排序。如果在程序执行过程中发生重排序,先执行语句2后执行语句1,会发生什么情况?

当线程1先执行语句2时,配置并未加载,而inited=true设置初始化完成了。线程2执行时,读取到inited=true,直接执行doSomething方法,而此时配置未加载,程序执行就会有问题

不保证原子性

volatile是不能保证原子性的,可使用原子类或者加锁

volatile实现原理

  • 有序性原理
graph LR
StoreStore屏障-->|前屏障|volatile写
volatile写-->|后屏障|StoreLoad屏障
volatile读-->|后屏障1|LoadLoad屏障
LoadLoad屏障-->|后屏障2|LoadStore屏障
  • 可见性原理
graph LR
volatile写-->StoreLoad屏障
StoreLoad屏障-->|Lock前缀指令|volatile读

Lock前缀的指令将该变量所在缓存行的数据写回到主内存中,并使其他处理器中缓存了该变量内存地址的数据失效

当其他线程读取volatile修饰的变量时,本地内存中的缓存失效,就会到到主内存中读取最新的数据

  • 总线风暴

基于 CPU 缓存一致性协议,JVM 实现了 volatile 的可见性。但由于总线嗅探机制,会不断的监听总线。如果大量使用 volatile,cas不断循环无效交互会导致总线带宽达到峰值,引起总线风暴。

伪共享问题

public class Share {
volatile int value;
}

volatile修饰解决了value内存可见性问题,但由于线程本地缓存是以缓存行为单位,可能会存储其他变量。因此,volatile带来的缓存失效会使同一缓存行上的其他变量也失效,访问时也需要从主从中再次获取,带来性能问题,此类问题称之为伪共享。

如何解决呢?

保证一个缓存行上只有一个share变量即可。早期版本JDK有使用无用变量作为填充物解决的,但是存在不同机器缓存行大小不一致、无用填充物被JVM优化掉等问题。基于此,在Java 8官方提供了Contended 注解,如下:

public class Share {
@Contended
volatile int value;
}

使用如上注解,需要在 JVM 启动参数中加入 -XX:-RestrictContended,这样 JVM 在运行时就会自动的为我们的 Share 类添加合适大小的填充物(padding)来解决伪共享问题。

final

基本特性

  • final变量只能被赋值一次,赋值后值不再改变(引用对象地址值不能改变,但内容可以变化)
  • final修饰的方法在编译阶段被静态绑定(static binding),不能被重写
  • final修饰的类不能被继承,所有成员方法都会被隐式地指定为final方法

并发final

graph LR
final写-->StoreStore屏障
StoreStore屏障-->B[Store 对象引用]

final变量赋值必须在所属对象引用获取前完成,通过在final写后面插入StoreStore屏障,禁止处理器把final域的写重排序到构造函数之外

graph LR
A[Load 对象引用]-->LoadLoad屏障
LoadLoad屏障-->final读

相反,final变量读取必须在所属对象引用获取后完成,通过在final读前面插入LoadLoad屏障,禁止读对象引用和读该对象final域重排序

public class FinalDemo {
private int a; // 普通域
private final int b; // final域
private static FinalDemo finalDemo; public FinalDemo() {
a = 1; // ①写普通域
b = 2; // ②写final域
} // 线程A先执行writer()方法
public static void writer() {
// 两个操作:
// 1)构造一个FinalExample类型的对象,①写普通域a=1,②写final域b=2
// 2)③把这个对象的引用赋值给引用变量finalDemo
finalDemo = new FinalDemo();
} // 线程B后执行reader()方法
public static void reader() {
FinalDemo demo = finalDemo; // ④读对象引用
int a = demo.a; // ⑤读普通域
int b = demo.b; // ⑥读final域
}
}

若示例中final int a变为引用类型final int[] arrays,在构造函数中初始化数组并赋值,赋值语句同样不会重排序到构造函数以外

Java并发编程之并发关键字的更多相关文章

  1. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  2. (转)Java并发编程:volatile关键字解析

    转:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或 ...

  3. Java并发编程:volatile关键字解析(转载)

    转自https://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析   Java并发编程:volatile关键字解析 ...

  4. Java并发编程:volatile关键字解析-转

    Java并发编程:volatile关键字解析 转自海子:https://www.cnblogs.com/dayanjing/p/9954562.html volatile这个关键字可能很多朋友都听说过 ...

  5. Java并发编程:volatile关键字解析(学习总结-海子)

    博文地址:Java并发编程:volatile关键字解析

  6. 6、Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  7. 转:Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字, ...

  8. [转载]Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  9. Java并发编程:并发容器之CopyOnWriteArrayList(转载)

    Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...

  10. Java并发编程:并发容器之CopyOnWriteArrayList

    转载: Java并发编程:并发容器之CopyOnWriteArrayList Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个 ...

随机推荐

  1. html5 image>usemap (attribute)

    # html5   image>usemap  (attribute) https://caniuse.com/#search=usemap http://www.w3.org/TR/html5 ...

  2. cache-control config & http cache storage location control

    cache-control config & http cache storage location control cache-control 设置 where is the storage ...

  3. 2019 front-end job Interview

    2019 front-end job Interview 2019 前端面试题 掘金 https://juejin.im/tag/面试 https://juejin.im/post/5c875791e ...

  4. git in depth

    git in depth git delete remote branch # Deleting remote branches in Git $ git push origin --delete f ...

  5. 解决ROS及Fast-RTPS安装和使用中raw.githubusercontent.com无法连接的问题

    资料参考: https://blog.csdn.net/weixin_44692299/article/details/105869229

  6. Linux之根目录说明

    文件系统 文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构:即在存储设备上组织文件的方法.操作系统中负责管理和存储文件信息的软件 ...

  7. 百度webuploader_c#实现文件分片上传思路

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. 创建一个springboot项目

    进入https://start.spring.io/ 再点击GENERATE,下载解压即可 注意配置好阿里云的仓库镜像,免得依赖无法下载 不下载最新版springboot是因为我在测试中遇到了问题,貌 ...

  9. 工作之余第二篇(看源码自己实现ArrayList和LinkList)

    先看源码: 首先看构造器,构造器有三种,一种直接给定初始长度的,如下代码 public ArrayList(int initialCapacity) { if (initialCapacity > ...

  10. Vue学习笔记-Vue.js-2.X 学习(一)===>基本知识学习

    一  使用环境: windows 7 64位操作系统 二  IDE:VSCode/PyCharm 三  Vue.js官网: https://cn.vuejs.org/ 四  下载安装引用 方式1:直接 ...