Java volatile关键字解惑
volatile特性
内存可见性:通俗来说就是,线程A对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的。
volatile的使用场景
通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中,volatile相当于一个轻量级的sychronize,
因为不会引起线程的上下文切换,但是使用volatile必须满足两个条件:
1、对变量的写操作不依赖当前值,如多线程下执行a++,是无法通过volatile保证结果准确性的;
2、该变量没有包含在具有其它变量的不变式中,这句话有点拗口,看代码比较直观。
public class NumberRange {
private volatile int lower = ;
private volatile int upper = ; public int getLower() { return lower; }
public int getUpper() { return upper; } public void setLower(int value) {
if (value > upper)
throw new IllegalArgumentException(...);
lower = value;
} public void setUpper(int value) {
if (value < lower)
throw new IllegalArgumentException(...);
upper = value;
}
}
上述代码中,上下界初始化分别为0和10,假设线程A和B在某一时刻同时执行了setLower(8)和setUpper(5),且都通过了不变式的检查,
设置了一个无效范围(8, 5),所以在这种场景下,需要通过sychronize保证方法setLower和setUpper在每一时刻只有一个线程能够执行。
下面是我们在项目中经常会用到volatile关键字的两个场景:
1、状态标记量
在高并发的场景中,通过一个boolean类型的变量isopen,控制代码是否走促销逻辑,该如何实现?
public class ServerHandler {
private volatile isopen;
public void run() {
if (isopen) {
//促销逻辑
} else {
//正常逻辑
}
}
public void setIsopen(boolean isopen) {
this.isopen = isopen
}
}
场景细节无需过分纠结,这里只是举个例子说明volatile的使用方法,用户的请求线程执行run方法,如果需要开启促销活动,
可以通过后台设置,具体实现可以发送一个请求,调用setIsopen方法并设置isopen为true,由于isopen是volatile修饰的,
所以一经修改,其他线程都可以拿到isopen的最新值,用户请求就可以执行促销逻辑了。
2、double check
单例模式的一种实现方式,但很多人会忽略volatile关键字,因为没有该关键字,程序也可以很好的运行,
只不过代码的稳定性总不是100%,说不定在未来的某个时刻,隐藏的bug就出来了。
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
syschronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
不过在众多单例模式的实现中,我比较推荐懒加载的优雅写法Initialization on Demand Holder(IODH)。
public class Singleton {
static class SingletonHolder {
static Singleton instance = new Singleton();
} public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
当然,如果不需要懒加载的话,直接初始化的效果更好。
如何保证内存可见性?
在java虚拟机的内存模型中,有主内存和工作内存的概念,每个线程对应一个工作内存,
并共享主内存的数据,下面看看操作普通变量和volatile变量有什么不同:
1、对于普通变量:读操作会优先读取工作内存的数据,如果工作内存中不存在,则从主内存中拷贝一份数据到工作内存中;
写操作只会修改工作内存的副本数据,这种情况下,其它线程就无法读取变量的最新值。
2、对于volatile变量,读操作时JMM会把工作内存中对应的值设为无效,要求线程从主内存中读取数据;
写操作时JMM会把工作内存中对应的数据刷新到主内存中,这种情况下,其它线程就可以读取变量的最新值。
volatile变量的内存可见性是基于内存屏障(Memory Barrier)实现的,什么是内存屏障?内存屏障,又称内存栅栏,是一个CPU指令。
在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM为了保证在不同的编译器和CPU上有相同的结果,
通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,
插入一条内存屏障会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
这段文字显得有点苍白无力,不如来段简明的代码:
class Singleton {
private volatile static Singleton instance;
private int a;
private int b;
private int b;
public static Singleton getInstance() {
if (instance == null) {
syschronized(Singleton.class) {
if (instance == null) {
a = ; //
b = ; //
instance = new Singleton(); //
c = a + b; //
}
}
}
return instance;
}
}
1、如果变量instance没有volatile修饰,语句1、2、3可以随意的进行重排序执行,即指令执行过程可能是3214或1324。
2、如果是volatile修饰的变量instance,会在语句3的前后各插入一个内存屏障。
通过观察volatile变量和普通变量所生成的汇编代码可以发现,操作volatile变量会多出一个lock前缀指令:
Java代码:
instance = new Singleton();
汇编代码:
0x01a3de1d: movb $0x0,0x1104800(%esi);
0x01a3de24: **lock** addl $0x0,(%esp);
这个lock前缀指令相当于上述的内存屏障,提供了以下保证:
1、将当前CPU缓存行的数据写回到主内存;
2、这个写回内存的操作会导致在其它CPU里缓存了该内存地址的数据无效。
CPU为了提高处理性能,并不直接和内存进行通信,而是将内存的数据读取到内部缓存(L1,L2)再进行操作,但操作完并不能确定何时写回到内存,如果对volatile变量进行写操作,当CPU执行到Lock前缀指令时,会将这个变量所在缓存行的数据写回到内存,不过还是存在一个问题,就算内存的数据是最新的,其它CPU缓存的还是旧值,所以为了保证各个CPU的缓存一致性,每个CPU通过嗅探在总线上传播的数据来检查自己缓存的数据有效性,当发现自己缓存行对应的内存地址的数据被修改,就会将该缓存行设置成无效状态,当CPU读取该变量时,发现所在的缓存行被设置为无效,就会重新从内存中读取数据到缓存中。
转载自:简书 占小狼 http://www.jianshu.com/p/195ae7c77afe
Java volatile关键字解惑的更多相关文章
- [Java并发编程(三)] Java volatile 关键字介绍
[Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...
- 13、Java并发性和多线程-Java Volatile关键字
以下内容转自http://tutorials.jenkov.com/java-concurrency/volatile.html(使用谷歌翻译): Java volatile关键字用于将Java变量标 ...
- Java Volatile关键字(转)
出处: Java Volatile关键字 Java的volatile关键字用于标记一个变量“应当存储在主存”.更确切地说,每次读取volatile变量,都应该从主存读取,而不是从CPU缓存读取.每次 ...
- Java volatile 关键字底层实现原理解析
本文转载自Java volatile 关键字底层实现原理解析 导语 在Java多线程并发编程中,volatile关键词扮演着重要角色,它是轻量级的synchronized,在多处理器开发中保证了共享变 ...
- Java volatile关键字详解
Java volatile关键字详解 volatile是java中的一个关键字,用于修饰变量.被此关键修饰的变量可以禁止对此变量操作的指令进行重排,还有保持内存的可见性. 简言之它的作用就是: 禁止指 ...
- Java Volatile关键字
在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写. 这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量 ...
- 从根源上解析 Java volatile 关键字的实现
1.解析概览 内存模型的相关概念 并发编程中的三个概念 Java内存模型 深入剖析Volatile关键字 使用volatile关键字的场景 2.内存模型的相关概念 缓存一致性问题.通常称这种被多个线程 ...
- java volatile关键字解析
volatile是什么 volatile在java语言中是一个关键字,用于修饰变量.被volatile修饰的变量后,表示这个变量在不同线程中是共享,编译器与运行时都会注意到这个变量是共享的,因此不会对 ...
- Java Volatile关键字 以及long,double在多线程中的应用
概念: volatile关键字,官方解释:volatile可以保证可见性.顺序性.一致性. 可见性:volatile修饰的对象在加载时会告知JVM,对象在CPU的缓存上对多个线程是同时可见的. 顺序性 ...
随机推荐
- centos 7 keepalived故障邮件通知实战(附Python邮件发送脚本)
centos 7 keepalived故障邮件通知实战(附Python邮件发送脚本) ##################### sendmail.py begin ######## ...
- 漫谈NIO(3)之Netty实现
1.前言 上一章结合Java的NIO例子,讲解了多路IO复用的一个基本使用方法,通过实际编码加深对其理解.本章开始进入Netty的环节,前面两章都是为了Netty进行铺垫说明.此节将对比Java的NI ...
- python的强制转换(当出现 not supported between instances of 'str' and 'int' 的错误时)
当我们编程时,有时会出现如下错误:TypeError: '>' not supported between instances of 'str' and 'int' 如下图: 这是因为input ...
- 二、源代码=>程序集及程序集概念介绍
文本脉络图如下: 一.源代码-面向CLR的编译器-托管模块-(元数据&IL代码)中介绍了编译器将源文件编译成托管模块(中间语言和元数据),本文主要介绍如何将托管模块合并成程序集. 1.程序集的 ...
- JVM-Java GC分析
如何获取JavaGC日志 用动态命令查看: jstat -gc 1262 2000 20 每隔20秒输入一次日志,总共输入20次 设置GC参数打印出日志 -XX:+PrintGC 输出GC日志 -X ...
- ambari 2.6.2 安装 hdp 2.6.5.0 遇到的问题
1.hive-client 无法安装 一直报错(symlink target already exists and it is not a symlink.),hive-client 已经存在且不是 ...
- 国内maven仓库地址资源汇总
国内maven仓库地址:阿里云maven仓库,网易163maven仓库,以及其他maven仓库地址. 国内下载maven一般速度都很慢,下载需要很久时间.这里汇总了一些国内的镜像资源 附带pom文件中 ...
- nuxt踩过的坑
nuxt.js 简单介绍 nuxt官网:https://zh.nuxtjs.org/ 1.nuxt.js的原理图: 具体的原理介绍官网有详细的解释,欢迎移步官网,这里不再复述. 2.nuxt.js的优 ...
- 面试题6:二叉树最近公共节点(LCA)《leetcode236》
Lowest Common Ancestor of a Binary Tree(二叉树的最近公共父亲节点) Given a binary tree, find the lowest common an ...
- Matlab基本数学应用
基本线性代数 [R jb]=rref(A)将A化为行最简型矩阵.R为所得行最简型矩阵,jb是一个向量显示每行首非0元所在列号. inv(A)求方阵A的逆,注意结果可能出现错误.当结果中出现Inf和Na ...