JMM(为什么需要volatile)

Java内存模型(JavaMemoryModel)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量,存储到内存和从内存中读取变量这样的底层细节。

JMM有以下规定:

所有的共享变量都存储于主内存,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

每一个变量的工作副本还存在线程的工作(本地)内存中.

线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。

不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。

本地内存和主内存的关系:

对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。

解决这种共享变量在多线程模型中的不可见性问题,有两种方法:
1.加锁:
某一个线程进入synchronized代码块前后,线程会获得锁,清空工作内存,从主内存拷贝共享变量最新的值到工作内存成为副本,执行代码,将修改后的副本的值刷新回主内存中,线程释放锁。

2.使用volatile

volatile关键字

1.保证此变量对所有线程的可见性

每个线程操作数据的时候会把数据从主内存读取到自己的工作内存,如果他操作了数据并且写会了,他其他已经读取的线程的变量副本就会失效了,需要都数据进行操作又要再次去主内存中读取了。

volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。

:由于volatile关键字只保证可见性,不保证原子性,我们仍然需要通过加锁(synchronized、lock、java并发包中的原子操作类)来保证原子性。

因为Java里面的运算并非原子操作。
以i++这句代码为例,在多线程的场景下,实际上i++不是原子操作,也就是说,它不是单独一条指令,而是3条指令(3条汇编指令)

  1. 从内存中把i的值取出来放到CPU的寄存器中

  2. CPU寄存器的值+1

  3. 把CPU寄存器的值写回内存

当AB线程读到的i都是0,之后AB为0加1,写回内存,则i做了两次加法,但是实际只加了一个1。

volatile关键字只保证了i的值在取值时是正确的,但无法保证原子性。

2.禁止指令重排序。

重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:

(1)重排序操作不会对存在数据依赖关系的操作进行重排序。

比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。

(2)重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变

比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。

使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则:

a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

b.在进行指令优化时,不能将对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。

例:

//x、y为非volatile变量
//flag为volatile变量
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会将语句3放到语句4、语句5后面。但是语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。

原理

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

(1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

(2)它会强制将对缓存的修改操作立即写入主存;

(3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

volatile改造单例模式

class Singleton{//保证可见性和指令重排private volatile static Singleton instence;//私有构造private Singleton(){};private Singleton getInstence(){if (instence == null){//同步锁定代码块synchronized (Singleton.class){if (instence == null){//非原子操作instence = new Singleton();}}}return instence;}
}

对象实际上创建对象要经过过如下几个步骤:

  1. 分配内存空间。
  2. 调用构造器,初始化实例。
  3. 返回地址给引用

所以创建的过程是可能发生指令重排序的,有可能3操作在2之前,此时还没真正的初始化完对象。

但是别的线程去判断instance!= null,直接拿去用了,其实这个对象是个半成品,那就有空指针异常了。

Java volatile关键字总结相关推荐

  1. Java volatile关键字原理解剖

    Java volatile关键字原理解剖 文章目录 Java volatile关键字原理解剖 参考文章 前置知识 CPU缓存模型 CPU缓存行 并发编程基本概念 Java锁概念 volatile关键字 ...

  2. [Java并发编程(三)] Java volatile 关键字介绍

    [Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...

  3. Java Volatile关键字可见性问题分析

    Java Volatile关键字可见性问题分析 Java 内容模型 普通变量(非Vola变量)的内存不可见性 Volatile变量的内存可见性 剩余疑惑 Java 内容模型 具体可以查看这篇文章Jav ...

  4. java volatile关键字的作用_java volatile关键字作用及使用场景详解

    1. volatile关键字的作用:保证了变量的可见性(visibility).被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象.如以下代码片段,isShut ...

  5. Java volatile关键字详解

    1.关于volatile volatile是Java语言中的关键字,用来修饰会被多线程访问的共享变量,是JVM提供的轻量级的同步机制,相比同步代码块或者重入锁有更好的性能.它主要有两重语义,一是保证多 ...

  6. volite java_如何理解 JAVA volatile 关键字

    最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂.所以学习过程中顺手翻译下来,一方面巩固知识,一方面希 ...

  7. java volatile关键字使用

    1.为什么要使用volatile关键字? 先看下面的代码: //线程1 boolean stop = false; while(!stop){doSomething(); }//线程2 stop = ...

  8. 一个具体的例子学习Java volatile关键字

    相信大多数Java程序员都学习过volatile这个关键字的用法.百度百科上对volatile的定义: volatile是一个类型修饰符(type specifier),被设计用来修饰被不同线程访问和 ...

  9. Java——volatile关键字详解

    关注微信公众号:CodingTechWork,一起学习进步. volatile介绍 volatile概述 volatile是比synchronized关键字更轻量级的同步机制,访问volatile变量 ...

最新文章

  1. python url请求
  2. Call 从一个批处理程序调用另一个批处理程序,并且不终止父批处理程序。
  3. html正则判断全数字,javascript如何判断是不是数字?
  4. 浪费了4年后,公司的产品小哥去快手搞 Java 了
  5. django-学生列表的删除操作
  6. 调用高德逆地理接口_地理编码与逆地理编码
  7. 什么是JDK JRE JVM?
  8. 使用C# Detach和Attach 数据库
  9. 【python|多进程】打印进度条
  10. python生成双层pdf
  11. 机器学习面试必知:学生t分布的神奇之处
  12. 桌面计算机找不到硬盘,bios找不到硬盘完美解决方法 选择STATConfigur
  13. 精选目标检测3——yolo1、yolo2、yolo3和SSD的网络结构汇总对比
  14. 电阻式温度计 - 铂电阻温度计 - PT100
  15. mysql mtq_第十六节:Mysql中的关键字
  16. 布衣联盟XP SP2之国兴奥运版
  17. Adobe Fireworks CS5 | Adobe Dreamweaver CS5 | Adobe Photoshop CS5 Extended 绿色精简版最新下载地址...
  18. 教程 2 || 10分钟成为简笔画达人,然后......
  19. WINDOWS PE制作 - 主引导记录(Master Boot Record)介绍
  20. Docker入门:边玩边学

热门文章

  1. 单片机按键开发库-支持连击、长按等操作
  2. i3 i5 i7的区别
  3. 【FPGA教程案例59】深度学习案例6——基于FPGA的CNN卷积神经网络之整体实现
  4. linux系统初始账号密码,linux系统初始化--关于系统账户
  5. uni-app 中定义全局变量globalData注意事项
  6. SQL两表关联更新UPDATE
  7. Selenium隐示和显性等待
  8. VGA, QVGA, HVGA, WVGA, FWVGA和iPhone显示分辨率
  9. linux手写软件,Write一款梦幻般的Linux手机笔记应用程序
  10. Ubuntu系统CMake Error: Generator: execution of make failed.