Java并发--volatile关键字
一、volatile的实现原理
synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁,而volatile就可以说是JVM提供的最轻量级的同步机制。JMM告诉我们,各个线程会将共享变量从主内存中拷贝到工作内存,然后执行引擎会基于工作内存中的数据进行操作处理。线程在工作内存进行操作后何时会写入主内存中?这个实际对普通变量没有规定的,而针对volatile修饰的变量给Java虚拟机特殊的约定,线程对volatile变量的修改会立刻被其他线程所感知,即不会出现数据脏读,从而保证数据的可见性。
被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读现象
在生成汇编代码时会在volatile修饰的共享变量进行写操作的时候回多出Lock前缀的指令,主要有两个方面的影响:
将当前处理器缓存行的数据写回系统内存;
这个写回内存的操作会使得其他CPU 里缓存了该内存地址的数据无效,当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。
这样volatile变量通过这样的机制就是的每个线程都能获得该变量的最新值
二、volatile能保证线程安全吗--原子操作(i++)
volatile并不能保证线程安全。volatile关键字保证可见性、有序性。单不保证原子性。
可见性
对一个volatile变量的读,总能看到(任意线程)对这个volatile变量的写入。
原子性
对任意单个volatile变量的读/写具有原子性,单类似于volatile++这种复合操作不具有原子性
多线程下自增
很多人认为,多线程下i++这个是多线程并发问题,在变量count之前加上volatile就可以避免这个问题,看看结果是不是复合我们的预期。
package passtra; public class Conter{ public volatile static int count=0; public static void inc(){
try {
Thread.sleep(1);
} catch (Exception e) {
// TODO: handle exception
}
count++;
} public static void main(String[] args) {
for(int i=0;i<1000;i++){
new Thread(new Runnable() { @Override
public void run() {
Conter.inc(); }
}).start();
}
System.err.println("运行结果:Counter.cont="+Conter.count);
}
}
运行结果:Counter.cont=980
运行结果不是我们期望的1000,每次运行的结果也不相同
原因分析
这是因为虽然volatile保证了内存可见性,每个线程拿到的值都是最新值,但是count++这个操作并不是原子的,这里面涉及到获取值、自增、赋值的操作并不能同时完成。所以每个线程最终赋值是会进行重复赋值
1、JVM运行时内存区域,其中有一个内存区域是JVM虚拟机栈,每个线程运行时都有一个线程栈(线程私有)
线程栈保证了线程运行时变量值信息
2、当线程访问某一个对象值得时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体指load到线程本地内存中,建立一个变量副本。
之后线程就不在和对象在堆内存中的变量值有任何关系了,而是直接修改副本变量的值
3、在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值写回到对象在堆中变量
这样在堆中的变量的值就发生了变化
4、交互图如下:
- read and load:从主存复制3变量到当前工作内存
- use and assign:执行代码,改变共享变量值
- store and write:线程本地工作内存数据刷新主存相关内容
其中:
1、use and assgin可以多次出现,但是这些操作并不是原子性的,也就是在read load之后,如果主内存count变量发生修改后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果和预期不会一样。
2、对于volatile修饰的变量,jvm只是保证从主内存加载到线程工作内存的值是最新的,例如:
线程A,线程B在进行read,load操作中,发现内存中count的值都是5,那么都会加载这个最新的值;
在线程A对count进行修改之后,会write到主内存中,主内存中的count变量就会变为6;
线程B对由于已经进行read load操作,在进行运算之后,也会更新主内存count的变量值为6;
导致两个线程及时使用volatile关键字修改之后,还会存在并发的情况
解决办法
1、可以使线程串行执行(其实就是单线程,没有发挥多线程的优势)
2、可以使用synchronized或者锁的范式保证原子性
3、使用Atomic包中的AtomicInteger来替换int,它利用CAS算法保证了原子性
三、volatile的防止指令重排应用--双重懒加载单利模式
package passtra; public class Singleton{ private static volatile Singleton singleton; private Singleton(){} public static Singleton getsingleton(){ if(singleton==null){
synchronized (Singleton.class) {
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
这里的volatile关键字就是为了防止指令重排。
如果不用volatile,singleton=new Singleton();这段代码其实分了三步:
分配内存空间(1)
初始化对象(2)
将singleton对象指向分配的内存地址(3)
加上volatile是为了让这三步操作顺序执行,反之有可能第二部在第三部之前执行,就有可能某个线程拿到的单利对象是还没有初始化的,以至于报错。
四、volatile在Java并发中的应用
volatile在Java并发中用的很多。比如:Atomic包中的value,以及AbstractQueuedLongSynchronizer中的state都是被定义为volatile来保证内存可见性
Java并发--volatile关键字的更多相关文章
- Java 并发 —— volatile 关键字
volatile 修饰变量等于向编译器传达如下两层含义: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 禁止进行指令重排序. volat ...
- Java并发——volatile关键字
什么是内存可见性? 这里就要提一下JMM(Java内存模型).当线程在运行的时候,并不是直接直接修改电脑主内存中的变量的值.线程间通讯也不是直接把一个线程的变量的值传给另一个线程,让其刷新变量.下面是 ...
- Java并发——volatile关键字的使用
volatile关键字的使用volatile关键字原理适合使用volatile关键字的情况当且仅当满足以下所有条件时,才==应该==使用volatile关键字:volatile关键字的作用volati ...
- 【转】java中volatile关键字的含义
java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...
- 转:java中volatile关键字的含义
转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...
- java 并发——volatile
java 并发--volatile 介绍 维基百科: volatile 是一个类型修饰符(type specifier).volatile 的作用是确保本条指令不会因编译器的优化而省略,且要求每次直接 ...
- Java并发-volatile的原理及用法
Java并发-volatile的原理及用法 volatile属性:可见性.保证有序性.不保证原子性.一.volatile可见性 在Java的内存中所有的变量都存在主内存中,每个线程有单独CPU缓存内存 ...
- Java并发——volatile的原理
111 Java并发——volatile的原理
- Java中Volatile关键字详解 (转自郑州的文武)
java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 一.基本概念 先补充一下概念:J ...
随机推荐
- CSS变形动画
CSS变形动画 前言 在开始介绍CSS变形动画之前,可以先了解一下学习了它之后能做什么,有什么用,这样你看这篇文章可能会有一些动力. 学习了CSS变形动画后,你可以为你的页面做出很多炫酷的效果,如一个 ...
- 性能测试必备知识(5)- 深入理解“CPU 上下文切换”
做性能测试的必备知识系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1806772.html 前言 上一篇文章中,举例了大量进程等待 CP ...
- Git日常操作指南
git status git add . git commit -m "注释" git stash # 每次 push 前 git pull --rebase // 如果有冲突,解 ...
- Day02_IP地址详解&进制转换&DOS基本命令与批处理
学于千峰教育开源课程 感谢 千峰教育官网 b站在线视频 IP地址详解 一.简单局域网的构成 局域网:一般称为内网 简单局域网的构成:交换机.网线.PC(其他IT终端) 交换机:用来组建内网的局域网的设 ...
- Django学习路28_ .html 文件继承及<block 标签>,include 'xxx.html'
在 templates 文件夹下创建 基类 base.html <!DOCTYPE html> <html lang="en"> <head> ...
- PHP key() 函数
------------恢复内容开始------------ 实例 从当前内部指针位置返回元素键名: <?php$people=array("Peter","Joe ...
- PHP preg_replace_callback() 函数
preg_replace_callback 函数执行一个正则表达式搜索并且使用一个回调进行替换.高佣联盟 www.cgewang.com 语法 mixed preg_replace_callback ...
- luogu P4769 [NOI2018]冒泡排序 结论 树状数组 卡特兰数
LINK:冒泡排序 神题. 可以想到爆搜 期望得分5~10分. 打成这个样子心态不得爆炸? 仔细分析 一个不合法序列还有什么标志. 容易想到某个数字离自己位置相反的方向多走了一步. 考虑单独对每个数字 ...
- NOI Online 游戏 树形dp 广义容斥/二项式反演
LINK:游戏 还是过于弱鸡 没看出来是个二项式反演,虽然学过一遍 但印象不深刻. 二项式反演:有两种形式 一种是以恰好和至多的转换 一种是恰好和至少得转换. 设\(f_i\)表示至多的方案数 \(g ...
- UOJ #22 UR #1 外星人
LINK:#22. UR #1 外星人 给出n个正整数数 一个初值x x要逐个对这些数字取模 问怎样排列使得最终结果最大 使结果最大的方案数又多少种? n<=1000,x<=5000. 考 ...