volatile学习
第一、java内存模型
共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。
从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,
每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。
本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
JMM关于同步的规定:
1 线程解锁前,必须把共享变量的值刷新回主内存
2线程加锁前,必须读取主内存的最新值到自己的工作内存
3加锁解锁是同一把锁
由于JVM运行程序的实体就是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方为称为栈空间),工作内存是每个线程的私有数据区域,
而Java内存模型总规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,
首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再讲变量写回主内存,不能直接操作主内存中的变量,
各个线程中的工作内存存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,这个就是可见性

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明这两个步骤:

如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。
当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,
可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
第二、Vilatile的特性
2.1.vilatile定义
volatile是java虚拟机提供的轻量级的同步机制
2.2.可见性
各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后写回到主内存中的。这就可能存在一个线程AAA修改了共享变量X的值但还未写回主内存时,
另外一个线程BBB有对主内存中同一个共享变量X进行操作,但此时A线程工作内存中共享变量x对线程B来说是并不可见的,这种工作内存与主内存同步延迟线程就造成了可见性问题,
代码案例:
class MyData{
volatile int number = 1;
public void updateNumber(){
this.number = 60;
}
}
/**
* 验证volatitle的可见性
*
* 1.1假如int number =0;number 变量之前根本没有添加volatile关键字修饰
1.2添加volatile关键修饰
*/
public class VolatileDemo {
public static void main(String[] args) throws Exception{
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t common in");
//暂停一会儿线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.updateNumber();
System.out.println(Thread.currentThread().getName()+"\t update number\t"+myData.number);
},"AA").start();
while (myData.number==1){
//等于0一直等待
}
System.out.println("main number over\t"+myData.number);
}
}
2.3.不保证原子性
原子性:不可分割,完整性,也即某个线程正在做某个业务时,中间不可以被加载或者被分割。需要整体完整要么同时成功,要么同时失败,
也就是最终一致性
验证可见性和不保证原子性
不保证原子性的解决方法:
1.加sync
2.直接使用JUC下AtomicInteger
class MyData{
volatile int number = 0;
public void addNumber(){
this.number = 60;
}
public void addAtomicity(){
this.number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addMyAtomic(){
atomicInteger.getAndIncrement();
}
}
/**
* 1验证volatile的可见性
* 1.1假如int number=0;number变量之前根本没有添加volatile关键字修饰,没有可见性
* 1.2添加了volatile,可以解决可见性问题
* 2验证volatitle不保证原子性
* 原子性:不可分割,完整性,也即某个线程正在做某个业务时,中间不可以被加载或者被分割。
* 需要整体完整要么同时成功,要么同时失败
*
*/
public class VolatileDemo01 {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
myData.addAtomicity();
myData.addMyAtomic();
}
}
}).start();
}
//需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果看是多少?
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"最终结果值:"+myData.number);
System.out.println(Thread.currentThread().getName()+"最终结果值:"+myData.atomicInteger);
}
//volatile可见保证可见性,及时通知其他线程,主物理内存的值以及被修改了
public static void seeVolatile(){
MyData myData = new MyData();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("修改之前的值:"+myData.number);
//等待三秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用改变的方法
myData.addNumber();
System.out.println("修改之后的值:"+myData.number);
}
}).start();
while(myData.number==0){
//一直循环等待
}
System.out.println(Thread.currentThread().getName()+":"+myData.number);
}
}
2.4.禁止指令重排
volatile 禁止指令重排
JMM 有序性
计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令重排,一般分为以下3种
源代码→编译器优化的重排→内存系统的重排→最终执行的指令
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排序时必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保住一致性是无法确定的,结果无法预测
禁止指令重排总结
volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象
先了解一下概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:
一是保证特定操作的执行顺序,
二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。
由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和折腾Memory Barrier指令重排序,
也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
JMM线程安全得到保证
工作内存和主内存同步延时现象导致的可见性问题,可以使用synchronize或者volatile关键字进行解决,他们都可以使一个线程修改后的变量立即对其他线程可见。
对于指令重排导致的可见性问题和有序性问题可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化
那些地方用过volatile
1.单例模式DCL代码 2单例模式volatile分析
public class VolatileDemo02 {
private static volatile VolatileDemo02 instance = null;
private VolatileDemo02(){
System.out.println(Thread.currentThread().getName()+"构造函数");
}
//DCL(Double Check Lock双端检测机制)
public static VolatileDemo02 getInstance(){
if (instance == null) {
synchronized (VolatileDemo02.class){
if (instance == null) {
instance = new VolatileDemo02();
}
}
}
return instance;
}
public static void main(String[] args) {
// System.out.println(VolatileDemo02.getInstance()==VolatileDemo02.getInstance());
// System.out.println(VolatileDemo02.getInstance()==VolatileDemo02.getInstance());
// System.out.println(VolatileDemo02.getInstance()==VolatileDemo02.getInstance());
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
VolatileDemo02.getInstance();
}
}).start();
}
}
}
DCL(Double Check Lock双端检测机制)
DCL(双端检索)机制不一定线程安全,原因四有指令重排序的存在,加入volatile可以禁止指令重排
原因在与某一个执行到第一次检测,读取到的instance不为null时间,instance的引用对象可能没有完成初始化。
instance = new SingletonDemo();可以分为以下三步完成(伪代码)
memory = allocate();//1.分配对象内存空间
instance(memory );//2初始化对象
instance = memory //3设置instance执行分配的内存地址,此时instance!=null
步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中没有变化,因此这种重排优化是允许的
memory = allocate();//1.分配对象内存空间
instance = memory //3设置instance执行分配的内存地址,此时instance!=null但是对象还没有初始化
instance(memory );//2初始化对象
但是指令重排只会保证串行语义的执行的一致性(单线程),但不会关系多线程间的语义一致性。
所以当一条数据线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。
2.5Volatile与Synchronized区别
(1)从而我们可以看出volatile虽然具有可见性但是并不能保证原子性。
(2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。
但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。
synchronized太重了
volatile学习的更多相关文章
- volatile 学习笔记
全面理解Java内存模型(JMM)及volatile关键字 正确使用 Volatile 变量 Java内存模型 在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步.通信是指线程之间 ...
- java 语言多线程可见性(synchronized 和 volatile 学习)
共享变量可见性实现的原理 java 语言层面支持的可见性实现方式: synchronized volatile 1. synchronized 的两条规定: 1 线程解锁前,必须把共享变量的最新值刷新 ...
- C++——volatile关键字的学习
首先声明一点,本文是关于volatile关键字的学习,学习内容主要是来自一些大牛的网络博客. 一篇是何登成先生的C/C++ Volatile关键词深度剖析(http://hedengcheng.com ...
- JAVA多线程基础学习三:volatile关键字
Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...
- Java多线程学习(三)volatile关键字
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- 【java并发编程艺术学习】(三)第二章 java并发机制的底层实现原理 学习记录(一) volatile
章节介绍 这一章节主要学习java并发机制的底层实现原理.主要学习volatile.synchronized和原子操作的实现原理.Java中的大部分容器和框架都依赖于此. Java代码 ==经过编译= ...
- 一个具体的例子学习Java volatile关键字
相信大多数Java程序员都学习过volatile这个关键字的用法.百度百科上对volatile的定义: volatile是一个类型修饰符(type specifier),被设计用来修饰被不同线程访问和 ...
- JVM学习(九)volatile应用
一.初认volatile 首先学习volatile关键字时,我们先简单的了解一下它能干啥: 工作内存与主内存同步延迟现象导致的可见性问题: 可通过synchronized或volatile关键字解决, ...
- JAVA多线程学习- 三:volatile关键字
Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...
随机推荐
- P1004方格取数
这是提高组得一道动态规划题,也是学习y氏思考法的第一道题. 题意为给定一个矩阵,里面存有一些数,你从左上角开始走到右下角,另一个人从右下角开始走到左上角,使得两个人取数之和最大,当然一个数只可以取走一 ...
- F. Greedy Sequence(主席树区间k的后继)(The Preliminary Contest for ICPC Asia Nanjing 2019)
题意: 查找区间k的后继. 思路: 直接主席树. #define IOS ios_base::sync_with_stdio(0); cin.tie(0); #include <cstdio&g ...
- expdp使用
原文:https://blog.csdn.net/zftang/article/details/6387325 ORACLE EXPDP命令使用详细相关参数以及导出示例: 1. DIRECTORY指定 ...
- java中如果删除导入的jar包,工程出现叹号解决方案
第一步:在工程上右键 第二步:选中build Path 第三步:选择Configue bulid path 第四步:选择liberary 第五步:鼠标点击带红色叉叉的 第六步:点击edit 第七步:点 ...
- PHP扩展开发01:第一个扩展
我们先假设业务场景,是需要有这么一个扩展,提供一个叫ccvita_string的函数,他的主要作用是返回一段字符.(这个业务场景实在太假,大家就这么看看吧)对应的PHP代码可能是这样: functio ...
- luogu题解 P1462 【通往奥格瑞玛的道路】二分+spfa
题目链接: https://www.luogu.org/problemnew/show/P1462 思路: 又是一道水题,很明显二分+最短路 而且这道题数据非常水,spfa有个小错误居然拿了91分还比 ...
- pycharm设置用滑轮改变字体大小
在电脑第一次安装pycharm之后,发现每次调整代码界面的字体,总是需要到setting里面调整,这样非常不方便,特别是对于代码量很多的时候,我们有时候需要把目光聚焦到某一句代码,这个时候就需要放大, ...
- Tomcat 7 自动加载类及检测文件变动原理
在一般的 web 应用开发里通常会使用开发工具(如 Eclipse.IntelJ )集成 tomcat ,这样可以将 web 工程项目直接发布到 tomcat 中,然后一键启动.经常遇到的一种情况是直 ...
- Vue 路由(对路由页面编写做规范)
前言 上一篇写了“Vue 路由拦截(对某些页面需要登陆才能访问)” 的博客,此篇是续上篇对路由页面模块化,可以看到之前的路由配置都写在main.js中,真正开发肯定不能都写在main.js,所以我们要 ...
- Slimvoice能代替JavaScript?
对于Slimvoice(https://slimvoice.co/),我想反对JavaScript的炒作,并对整个应用程序进行服务器端渲染.您可能会说:“用户必须在使用应用程序时重新加载每个页面,这必 ...