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. POJ3233 构造子矩阵+矩阵快速幂

    题意:给你矩阵A,求S=A+A^1+A^2+...+A^n sol:直接把每一项解出来显然是不行的,也没必要. 我们可以YY一个矩阵: 其中1表示单位矩阵 然后容易得到: 可以看出这个分块矩阵的左下角 ...

  2. 使用 Jenkins 搭建 CI/CD All In One

    使用 Jenkins 搭建 CI/CD All In One https://ci.jenkins.io/ https://www.jenkins.io/zh/ jobs pipelines refs ...

  3. 微信分享 API

    微信分享 API https://market.cmbchina.com/MPage/online/190416201200302/wechatShare.js /* * 注意: * 1. 所有的JS ...

  4. GitHub Actions in Action

    GitHub Actions in Action https://lab.github.com/githubtraining/github-actions:-hello-world https://g ...

  5. free website generator by google

    free website generator by google https://sites.google.com/view/webgeeker-xyz/首页 https://sites.google ...

  6. html fragment & html template & virtual DOM & web components

    html fragment & html template & virtual DOM https://developer.mozilla.org/en-US/docs/Web/API ...

  7. 看超额担保免信任的NGK DeFi 乐高如何打造下一个千倍币?

    2020年中,DeFi的高收益率吸引了大量热钱涌入,DeFi总锁仓量破百亿美金.如今,流动性挖矿的热潮暂时停歇,但对于 NGK DeFi项目来说,它背后的演变进化从未停止. 免信任是 NGK DeFi ...

  8. 「NGK每日快讯」11.28日NGK公链第25期官方快讯!

  9. js中this指向的问题与联系

    前言 JavaScript 中最大的一个安全问题,也是最令人困惑的一个问题,就是在某些情况下this的值是如何确定的.有js基础的同学面对这个问题基本可以想到:this的指向和函数调用的方式相关.这当 ...

  10. Docker备份迁移

    目录 Docker备份迁移 1.容器保存为镜像 2.镜像打包成压缩文件 3.把压缩文件恢复成镜像 Docker备份迁移 1.容器保存为镜像 将已经装好各种软件的容器再次打包为镜像,这样下次直接装这个镜 ...