volatile关键字的2个作用

1.线程的可见性

2.防止指令重排

什么是线程的可见性?

线程的可见性 就是一个线程对一个变量进行更改操作 其他线程获取会获得最新的值。

线程在执行的行 操作主线程的变量。会将变量的副本拷贝一份到线程的工作区域(避免每次到主线程读取 提高效率),在更改后的一段时间内写入主内存

如下示例代码:

public class Accounting implements Runnable {
boolean quit=false;
int i=0;
@Override
public void run() {
while (!quit){
i++;
}
System.out.println("线程退出");
}
public static void main(String[] args) throws InterruptedException {
Accounting accounting = new Accounting();
Thread a1 = new Thread(accounting, "a1");
Thread a2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("开始通知线程结束");
accounting.setQuit(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
a2.start();
a1.start();
Thread.sleep(1000);
} public boolean isQuit() {
return quit;
} public void setQuit(boolean quit) {
this.quit = quit;
}
}

这段代码的逻辑就是线程a1 执行循环操作  a2 2秒后设置quit为true任务结束 打印 "线程退出";

那么真的能够成功退出吗?我们看看 线程执行在内存中的操作图

打印:

开始通知线程结束

a2 线程首先将自己工作线程的quit改为ture ,然后一定时间之后去将主内存的quit改为true  ,但是a1线程始终是操作的是自己的工作内存的副本 所以死循环

这个时候在quit加上volatile关键字

  volatile boolean quit=false;

打印

开始通知线程结束
线程退出

加上volatile关键字后。当一个线程对变量进行修改会更新自己的工作内存里面的值,然后立即将改动的值刷新到主内存,同时线程2的工作内存的quit副本缓存失效  下次直接到主内存读取  所以能够正常执行

记录一个小插曲

System.out.println,sychronized,Thread.sleep Thread.sleep 影响可见性?

System.out.println

public class Accounting implements Runnable {
boolean quit=false;
int i=0;
@Override
public void run() {

while (!quit){
i++;
System.out.println(i);
}
System.out.println("线程退出");
}
public static void main(String[] args) throws InterruptedException {
Accounting accounting = new Accounting();
Thread a1 = new Thread(accounting, "a1");
Thread a2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("开始通知线程结束");
accounting.setQuit(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
a2.start();
a1.start();
Thread.sleep(1000); } public boolean isQuit() {
return quit;
} public void setQuit(boolean quit) {
this.quit = quit;
}

会发现没有加上volatile一样可以成功退出 。那我们上面说的 线程的内存处理 不成立了吗?

查资料说 是因为jvm对锁的优化。因为如果我们在循环里面加上sychronize同步锁 会产生大量的锁竞争 所以jvm优化过后

   synchronized (this){
while (!quit){
//.....
}
}

但是我们并没有在while里面加锁啊。我们看看打印的方法源码

    public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}

sleep方法并没有加锁,为什么能够保证可见性

sleep是阻塞线程并不释放锁,让出cpu调度。 让出cpu调度后下次执行会刷新工作内存

指令重排

指令重排指在编译的时候,在不单线程运行不影响结果的情况下进行指令优化

如:

public class Context {
boolean isLoad=false;
Object configuration=null;
public void loadConfiguration(){
System.out.println("正在加载配置文件");
configuration= new Object();
isLoad=true;
} public void initContext(){
System.out.println("正在进行初始化");
} public static void main(String[] args) {
Context context=new Context();
context.loadConfiguration();
if(context.isLoad){
context.initContext();
}
}
}

这段代码就是先加载配置文件信息  然后初始化上下文

我们在单线程下 把他们的顺序调换模拟指令重排 会对结果没有影响

  public void   loadConfiguration(){
isLoad=true;
System.out.println("正在加载配置文件");
configuration= new Object(); }

但是在多线程下面

public class Context {
boolean isLoad=false;
Object configuration=null;
public void loadConfiguration(){
//模拟jvm指令重排 将isLoad命令排在第一位
isLoad=true;
/***
* 模拟并发情况下指令重排。导致的isload=true排到前面。
* 这个时候配置文件没初始化。initContext监听到lsLoad等于true根据配置文件进行初始化
*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
configuration= new Object();
//isLoad=true;指令重排前
} public void initContext(){
configuration.toString();
System.out.println("正在进行初始化");
} public static void main(String[] args) {
Context context=new Context();
//负责监听 如果加载完毕 则进行上下午初始化
Thread t2=new Thread(new Runnable() {
@Override
public void run() { while (true){
if(context.isLoad){
context.initContext();
break;
}
} }
},"t2");
//负责加载配置文件
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
context.loadConfiguration();
}
},"t1");
t1.start();
t2.start();
}
}

只是模拟指令重排 先不考虑可见性  这种情况会初始化context 没有configuration 报错  使用volatile关键字修饰可以避免

值得注意的一点

volatile虽然能够保证线程的可见性 但是并不能保证原子性  比如i++操作 都是读出i的值 进行运算再写入。如果在读出的时候别的线程改变了 就会不一致

哪种场景适合用volatile 对一个变量的值进行修改 不依赖其他值。 比如 index=true   而不是i=i+j;或则index=j>a   或 a=j (会从内存中读出j的值 然后赋值到a);

java提供atomic cas能够性能比锁高能够保证原子性 如:atomicInt atomictDouble

volatile可见性和指令重排的更多相关文章

  1. Java并发:volatile内存可见性和指令重排

    volatile两大作用 1.保证内存可见性 2.防止指令重排 此外需注意volatile并不保证操作的原子性. (一)内存可见性 1 概念 JVM内存模型:主内存和线程独立的工作内存 Java内存模 ...

  2. volatile关键字?MESI协议?指令重排?内存屏障?这都是啥玩意

    一.摘要 三级缓存,MESI缓存一致性协议,指令重排,内存屏障,JMM,volatile.单拿一个出来,想必大家对这些概念应该有一定了解.但是这些东西有什么必然的联系,或者他们之间究竟有什么前世今生想 ...

  3. Java内存模型与指令重排

    Java内存模型与指令重排 本文暂不讲JMM(Java Memory Model)中的主存, 工作内存以及数据如何在其中流转等等, 这些本身还牵扯到硬件内存架构, 直接上手容易绕晕, 先从以下几个点探 ...

  4. 轻量级的同步机制——volatile语义详解(可见性保证+禁止指令重排)

    目录 1.关于volatile 2.语义一:内存可见性 2.1 一个例子 2.2 java的内存模型(JMM) 2.3 happens-before规则 2.4 volatile解决内存可见性问题的原 ...

  5. jvm(三)指令重排 & 内存屏障 & 可见性 & volatile & happen before

    参考文档: https://tech.meituan.com/java-memory-reordering.html http://0xffffff.org/2017/02/21/40-atomic- ...

  6. JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁

    问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...

  7. 一篇讲Java指令重排和内存可见性的好文

    在这里: http://tech.meituan.com/java-memory-reordering.html 指令重排和内存可见性(缓存不一致)是两个不同的问题. volatile关键字太强,即阻 ...

  8. 多线程的指令重排问题:as-if-serial语义,happens-before语义;volatile关键字,volatile和synchronized的区别

    一.指令重排问题 你写的代码有可能,根本没有按照你期望的顺序执行,因为编译器和 CPU 会尝试指令重排来让代码运行更高效,这就是指令重排. 1.1 虚拟机层面 我们都知道CPU执行指令的时候,访问内存 ...

  9. JVM内存模型、指令重排、内存屏障概念解析

    在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...

随机推荐

  1. 贪吃蛇c++实现

    近期没事翻了一下曾经写的程序.真是不堪入目.曾经真是什么都不懂.只是有一个程序倒是挺有意思的,大二的时候写的一个贪吃蛇游戏.尽管程序非常难看,还有非常多漏洞.但也是这个程序让我真正開始喜欢上了编程.不 ...

  2. 【cl】本地安装maven的jar包报错Artifact is already in the local repository

    原因是我直接把jar包放在了仓库里面 解决办法:将jar办放在不是仓库路径位置,在进行install就okle

  3. Java面试-Struts2

    1  Struts2工作原理 一个请求在Struts2框架中的处理大概分为下面几个步骤: 1.client初始化一个指向Servlet容器(比如Tomcat)的请求: 2.这个请求经过一系列的过滤器( ...

  4. 神经网络中的激活函数——加入一些非线性的激活函数,整个网络中就引入了非线性部分,sigmoid 和 tanh作为激活函数的话,一定要注意一定要对 input 进行归一话,但是 ReLU 并不需要输入归一化

    1 什么是激活函数? 激活函数,并不是去激活什么,而是指如何把“激活的神经元的特征”通过函数把特征保留并映射出来(保留特征,去除一些数据中是的冗余),这是神经网络能解决非线性问题关键. 目前知道的激活 ...

  5. 软件-集成开发环境:IDE

    ylbtech-软件-集成开发环境:IDE 集成开发环境(IDE,Integrated Development Environment )是用于提供程序开发环境的应用程序,一般包括代码编辑器.编译器. ...

  6. JXOI2019 退役记

    day0 考前一天在机房和RyeCatcher,还有高一数竞大佬wyt一起颓三国杀,被深深吸引无法自拔,所谓大考大浪,也算是缓解缓解压力 刷刷空间发现好多外地OIer都赶到江科了,萌生出去见一见我江西 ...

  7. 关于作者&情况

    本校第一次做信奥 , 如有错误, 见谅 本人之前从未接触编程, 选择信奥也只是因为怕被其他奥赛给淘汰... 这应该是懦弱吧...... 但自从接触编程以来, 虽然算不上极大的热爱, 但发自内心地喜欢它 ...

  8. shp系列(六)——利用C++进行Dbf文件的写(创建)

    上一篇介绍了shp文件的创建,接下来介绍dbf的创建. 推荐结合读取dbf的博客一起看! 推荐结合读取dbf的博客一起看! 推荐结合读取dbf的博客一起看! 1.Dbf头文件的创建 Dbf头文件的结构 ...

  9. numpy快速指南

    Quickstart tutorial 引用https://docs.scipy.org/doc/numpy-dev/user/quickstart.html Prerequisites Before ...

  10. NOIP2013 D2T1 积木大赛

    [NOIP2013T4]积木大赛 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 noip2013day2 描述 春春幼儿园举办了一年一度的"积木大 ...