Java并发篇_volatile

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

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

一、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。

img

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

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

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

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

二、volatile不适用的场景

(1)volatile不适合复合操作

例如,inc++不是一个原子性操作,可以由读取、加、赋值3步组成,所以结果并不能达到30000。.

img

(2)解决方法

1.采用synchronized

img

2.采用Lock

img

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

img

三、volatile原理

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

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

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

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

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

四、思考:单例模式的双重锁为什么要加volatile

img

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

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

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

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

上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/535657.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vi 语法着色

我所在部门的经理极其鄙视我用vi,这到不是说他看不惯vi,而是因为那句话"只有黑客级的人才用VI".而我只是一只小小莱鸟.所以只好被他们鄙视了. 现在说一说vi 着色的问题. 首先安装 vim-enhanced , # yum -y install vim-enhanced 然后, # vi ~/…

Docker Dockerfile详解

一、什么是Dockerfile Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像。 docker build命令用于从Dockerfile构建映像。可以在docker build命令中使用-f标志指向文件系统中任何位置的Doc…

公司台湾主站的url重写

今天对公司台湾主站的url地址进行优化.主站采用的是joomla,而joomla初建好后用的url对搜索引擎非常的不友好. Joomla中的SEF说白了就是一个对URL的重写的过程将原来参数众多,层次很深的URL改写为一个简单的更容易被记住被搜索的URL。通过分析Joomla站点的URL结果就…

编写第一个Spring程序——IOC实现

第一个Spring程序 IOC范例 1、新建maven工程 2、在pom.xml文件中导入相关jar包 <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --><dependency><groupId>org.springframework</groupId><artifactId>spring-core&l…

改变centos系统的时区

两条命令都可以: 1.timeconfig 2.tzselect

分布式文件系统:原理、问题与方法

本地文件系统如ext3&#xff0c;reiserfs等&#xff08;这里不讨论基于内存的文件系统&#xff09;&#xff0c;它们管理本地的磁盘存储资源、提供文件到存储位置的映射&#xff0c;并抽象出一套文件访问接口供用户使用。但随着互联网企业的高速发展&#xff0c;这些企业对数据…

编写第二个Spring程序——AOP实现

第二个Spring程序 AOP范例 1、新建maven工程 2、在pom.xml文件导入相关jar包 <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --><dependency><groupId>org.springframework</groupId><artifactId>spring-core<…

linux高负载下彻底优化mysql数据库

同时在线访问量继续增大 对于1G内存的服务器明显感觉到吃力严重时甚至每天都会死机 或者时不时的服务器卡一下 这个问题曾经困扰了我半个多月MySQL使用是很具伸缩性的算法&#xff0c;因此你通常能用很少的内存运行或给MySQL更多的被存以得到更好的性能。 安装好mysql后&#x…

Java注释说明以及IDEA中的快捷键

一、单行注释 说明&#xff1a;单行注释 一般注释少量的代码或者说明内容 格式&#xff1a;//注释的内容 IDEA中的快捷键&#xff1a;使用Ctrl /&#xff0c; 添加行注释&#xff0c;再次使用&#xff0c;去掉行注释 二、多行注释 说明&#xff1a;多行注释 一般注释大量的…

redhat系统双网卡绑定

Redhat Linux的网络配置&#xff0c;基本上是通过修改几个配置文件来实现的&#xff0c;虽然也可以用ifconfig来设置IP&#xff0c;用route来配置默认网关&#xff0c;用hostname来配置主机名&#xff0c;但是重启后会丢失。 1.相关的配置文件: /ect/hosts 配置主机名和IP地址…

JDK源码解析之java.util.Iterator和java.lang.Iterable

在Java中&#xff0c;我们可以对List集合进行如下几种方式的遍历&#xff1a;第一种就是普通的for循环&#xff0c;第二种为迭代器遍历&#xff0c;第三种是for each循环。后面两种方式涉及到Java中的iterator和iterable对象&#xff0c;接下来我们通过源码来看看这两个对象的区…

为了让你的网页能在更多的服务器上正常地显示,还是加上“SET NAMES UTF8”吧

Repinted:http://blog.csdn.net/class1/archive/2006/12/30/1469298.aspx 为了让你的网页能在更多的服务器上正常地显示&#xff0c;还是加上“SET NAMES UTF8”吧(可以根据你的喜欢选择相应的编码,如gb2312)&#xff0c;即使你现在没有加上这句也能正常访问。 先说MySQL的字…

WebLogic11g 安装配置规范

目录 1 文档控制... 3 1.1 修改记录... 3 1.2 分发者... 3 1.3 审阅记录... 3 1.4 相关文档... 3 2 安装准备... 4 2.1 安装前需要开发单位提供的信息... 4 2.2 本地磁盘空间配置规范... 4 2.3 版本要求规范... 4 2.4 weblogic部署配置规范... 5 2.4.1操作系统要求.…

JDK源码解析之java.util.ListIterator

ListIterator是一个功能更加强大的迭代器接口, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。 一、源码解…

VsFTP出现500 OOPS: cannot change directory的解决办法

cannot change directory:/home/*** ftp服务器连接失败,错误提示:500 OOPS: cannot change directory:/home/*******500 OOPS: child died解决方法:在终端输入命令&#xff1a;setsebool ftpd_disable_trans 1 service vsftpd restart就&#xff2f;&#xff2b;了&#xff01;…

Oracle的reman命令

list命令&#xff1a; list backupset summary 列出概要信息 list backupset by file list archivelog all 列出所有归档日志 list backupset tag 00列出标签信息 list backupset 8 列出8号…

Ubuntu root账号的使用

第一次安装好Ubuntu后&#xff0c;root帐号不能用。在安装期间创建的第一个用户对系统有管理权&#xff0c;通过“sudo”能象root运行程序.使用时仅需它的普通用户密码。例如: sudo apt-get update  如果你希望像传统 UNIX 样式使用root帐号。你能通过输入 sudo passwd root …

JDK源码解析之Java.util.Collection

Collection是单例集合的顶层接口&#xff0c;它表示一组对象&#xff0c;这些对象也称为Collection的元素&#xff0c;JDK 不提供此接口的任何直接实现&#xff0c;它提供更具体的子接口&#xff08;如Set和List&#xff09;实现 一、源码解析 1、接口定义 public interface …

Vim 命令操作

vim命令操作命令模式dd 编辑模式 末行模式 1.地址定界&#xff1a; startpos,endpos #:特定的第#行&#xff0c;例如S即第5行;:当前行;$:最后一行; #,#:指定行范圃,左侧起始行&#xff0…

JDK源码解析之Java.util.Collections

java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化&#xff0c;就像一个工具类,服务于Java的Collection框架。 一、源码解析 1、不可实例化 private Collections() {}Collections是util包中一个不可实例化的类。 2、优化参数 pri…