一、简介

volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

二、并发编程的3个基本概念

(1)原子性

定义: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。Java中的原子性操作包括:

a. 基本类型的读取和赋值操作,且赋值必须是数字赋值给变量,变量之间的相互赋值不是原子性操作。

b.所有引用reference的赋值操作

c.java.concurrent.Atomic.* 包中所有类的一切操作

(2)可见性

定义:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。当然,synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

(3)有序性

定义:即程序执行的顺序按照代码的先后顺序执行。

Java内存模型中的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。

在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

三、锁的互斥和可见性

锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。

(1)互斥即一次只允许一个线程持有某个特定的锁,一次就只有一个线程能够使用该共享数据。

(2)可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。也即当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的。如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

a.对变量的写操作不依赖于当前值。

b.该变量没有包含在具有其他变量的不变式中。

实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。事实上就是保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

四、Java的内存模型JMM以及共享变量的可见性

JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。

对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程模型中的不可见性问题,较粗暴的方式自然就是加锁,但是此处使用synchronized或者Lock这些方式太重量级了,比较合理的方式其实就是volatile。

需要注意的是,JMM是个抽象的内存模型,所以所谓的本地内存,主内存都是抽象概念,并不一定就真实的对应cpu缓存和物理内存

五、volatile变量的特性

(1)保证可见性,不保证原子性

a.当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;

b.这个写会操作会导致其他线程中的缓存无效。

(2)禁止指令重排

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

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

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

行时这两个操作不会被重排序。

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

比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发

生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。

重排序在单线程下一定能保证结果的正确性,但是在多线程环境下,可能发生重排序,影响结果,下例中的1和2由于不存在数据依赖关系,则有可能会被重排序,先执行status=true再执行a=2。而此时线程B会顺利到达4处,而线程A中a=2这个操作还未被执行,所以b=a+1的结果也有可能依然等于2。

.

使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则: a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后 面的操作可见;在其后面的操作肯定还没有进行; b.在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放 到其前面执行。 即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变 量及其后面语句可见。

六、volatile不适用的场景

(1)volatile不适合复合操作 例如,in c++不是一个原子性操作,可以由读取、加、赋值3步组成,所以结果并不能达到30000。

.

(2)解决方法

1.采用synchronized

2.采用Lock

3.采用java并发包中的原子操作类,原子操作类是通过CAS循环的方式来保证其原子性的

七、volatile原理

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

I. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内

存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

II. 它会强制将对缓存的修改操作立即写入主存;

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

八、单例模式的双重锁为什么要加volatile

需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。instance = new TestInstance();可以分解为3行伪代码

1.memory = allocate()  //分配内存

2. ctorInstanc(memory)  //初始化对象

3. instance = memory  //设置instance指向刚分配的地址

上面的代码在编译运行时,可能会出现重排序从1-2-3排序为1-3-2。在多线程的情况下会出现以下问题。线程A在执行第5行代码时,B线程进来,而此时A执行了1和3,没有执行2,此时B线程判断instance不为null,直接返回一个未初始化的对象。

java allocate_Java中volatile关键字的最全总结相关推荐

  1. Java中 volatile 关键字的最全总结,快给自己查缺补漏吧!

    作者:老鼠只爱大米 来源:blog.csdn.net/u012723673/article/details/80682208 一.简介 volatile是Java提供的一种轻量级的同步机制.Java ...

  2. Java中 volatile 关键字的最全总结,赶快给自己查缺补漏吧!

    作者:老鼠只爱大米 blog.csdn.net/u012723673/article/details/80682208 一.简介 volatile是Java提供的一种轻量级的同步机制.Java 语言包 ...

  3. java多线程中volatile关键字

    一:计算机中的内存模型 计算机中指令都通过CPU去执行,执行执行的时候一般都会涉及到读写,我们都知道CUP的计算速度是很快的,如果都把数据放到我们的主存中则会造成CPU每执行一条指令都要等待的问题,这 ...

  4. java中实现具有传递性吗_Java中volatile关键字详解,jvm内存模型,原子性、可见性、有序性...

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

  5. java中volatile关键字

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

  6. java关键字 valotile_Java内存模型-jsr133规范介绍,java中volatile关键字的含义

    最近在看<深入理解Java虚拟机:JVM高级特性与最佳实践>讲到了线程相关的细节知识,里面讲述了关于java内存模型,也就是jsr 133定义的规范. 系统的看了jsr 133规范的前面几 ...

  7. java中volatile_java中volatile关键字的含义

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语 ...

  8. null在java存在的意义何在,Java并发编程——volatile关键字

    一.volatile是什么 volatile是Java并发编程中重要的一个关键字,被比喻为"轻量级的synchronized",与synchronized不同的是,volatile ...

  9. Java并发编程—volatile关键字(保证变量的可见性、有序性机制)

    原文作者:Matrix海子 原文地址:Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程 ...

最新文章

  1. javaWeb -- HTTP协议
  2. installshield 安装文件属性的原始文件名 如何修改_iPhone修改微信提示音,iOS12可用,无需越狱详细教程...
  3. 【Leetcode】几种简单的排序算法
  4. 教你理解复杂的C/C++声明
  5. 管程,进程及线程之间的区别
  6. 小a的计算器(牛客签到题A)-简单模拟
  7. jsf集成spring_Spring和JSF集成:转换器
  8. java 数据结构 数据库_数据库索引背后的数据结构
  9. windows 覆盖linux,您是否曾考虑过用Linux替换Windows?
  10. 【转】XSD (xml Schema Definition)
  11. 职场 PUA 你知多少?
  12. 二级计算机vf题型,计算机二级VF考试常见题型与解题技巧
  13. webrtc中GCC拥塞控制模块之码率控制器AimdRateControl
  14. 简单的玻璃材质效果——UnityShader学习笔记
  15. 假设检验3- 卡方分布
  16. dos下用move命令移动文件夹
  17. unicloud云开发---uniapp云开发(四)---本机手机号一键登录以及第三方登陆
  18. cuteftp不能连接虚拟机的解决方法
  19. 柠萌影业三次冲刺IPO,爆款制造机的压力大爆了
  20. 打车软件盛行,如何判断是否拒载

热门文章

  1. 收集常用的CSS基础面试题
  2. NetFramework各个版本的特性笔记
  3. 从零开始学python人工智能课程_从零开始如何学习人工智能?
  4. jdk环境变量配置_jmeter及jdk的环境变量配置
  5. Linux c编译库路径,【一点一点学Linux C】交叉编译时候如何配置连接库的搜索路径...
  6. java项目001.双色球游戏
  7. 【MySQL】redo log --- 刷入磁盘过程
  8. IDEA设置类、方法注释模板
  9. Python 提取数据库(Postgresql)并邮件发送
  10. MySQL数据库服务器优化详细