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. mitmproxy 代理工具介绍:rewrite和map local实现

    在接口测试中,会用到抓包工具或者代理工具,常用代理工具包括charles. burpsuite. fiddler.mitmproxy等,ssh -D参数 可实现socks5代理.网络嗅探工具可以使用t ...

  2. Web API 设计

    Web API 设计 The Design of Web APIs free online ebook https://www.manning.com/books/the-design-of-web- ...

  3. React render twice bug

    React render twice bug React bug constructor render twice bug update render twice bug StrictMode htt ...

  4. how to measure function performance in javascript

    how to measure function performance in javascript Performance API Performance Timeline API Navigatio ...

  5. Docker & Node.js

    Docker & Node.js https://nodejs.org/en/docs/guides/nodejs-docker-webapp/ https://docs.docker.com ...

  6. let & var & initialized bug

    let & var & initialized bug what's wrong with this? https://github.com/lydiahallie/javascrip ...

  7. SVG 2 & SVG & getPointAtLength & getPathSegAtLength

    SVG 2 & SVG & getPointAtLength & getPathSegAtLength getPointAtLength SVG 1.x https://dev ...

  8. ROS 安装完成后运行小乌龟示例程序

    安装ROS成功后,在Beginner Tutorials中有一个简单的示例程序. 在Terminal中运行以下命令: $ roscore 新开一个terminal,运行以下命令,打开小乌龟窗口: $ ...

  9. JDBC概念理解

    ##JDBC: 概念:Java DataBase Connectivity  Java 数据库连接  Java语言操作数据库 JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则 ...

  10. Redis 内存淘汰机制详解

    一般来说,缓存的容量是小于数据总量的,所以,当缓存数据越来越多,Redis 不可避免的会被写满,这时候就涉及到 Redis 的内存淘汰机制了.我们需要选定某种策略将"不重要"的数据 ...