java 中violate_Java中的Volatile关键字

Java的volatile关键字用于标记一个Java变量为“在主存中存储”。更确切的说,对volatile变量的读取会从计算机的主存中读取,而不是从CPU缓存中读取,对volatile变量的写入会写入到主存中,而不只是写入到CPU缓存。

实际上,从Java5开始,volatile关键字不只是保证了volatile变量在主存中写入和读取,我回在后面的部分做相关的解释。

变量可见性问题

Java的volatile关键字保证了多个线程对变量值变化的可见性。这听起来有点抽象,让我来详细解释。

在一个多线程的程序中,当多个线程操作非volatile变量时,出于性能原因,每个线程会从主存中拷贝一份变量副本到一个CPU缓存中。如果你的计算机有多于一个CPU,每个线程可能会在不同的CPU中运行。这意味着每个简称拷贝变量到不同CPU的缓存中,如下图:

86d3972a2536f84f1b61783713ed3d46.png

对于非volatile变量,并没有保证何时JVM从主存中读取数据到CPU缓存,或者从CPU缓存中写出数据到主存。这会导致一些问题。

想象一种情况,多于一个线程访问一个共享对象,这个共享对象包含一个计数变量如下声明:

public class ShareObject {

public int counter = 0;

}

考虑只有一个线程Thread1增加counter这个变量的值,但是Tread1和Thread2可能有时会读取counter变量。

如果counter变量没有被声明为volatile,就不能保证何时这个变量的值会从CPU缓存写回主存,这意味着,在CPU缓存中的counter变量的值可能和主存中的不一样。如下图所示:

5684ed01d94043a545a866fd233a93aa.png

线程没有看到一个变量最新更新的值的原因是这个变量还没有被一个线程写回到主存,这被称为“可见性”问题。一个线程对变量的更新对其他线程不可见。

Java的volatile可见性保证

Java的volatile关键字想要解决变量可见性问题。通过声明counter变量为volatile,所有对counter变量的写入都回立即写回到主存,同时所有对counter变量也都会从主存中读取。

西面的代码展示了如何把counter变量声明为volatile:

public class SharedObject {

public volatile int counter = 0;

}

声明一个变量为volatile保证了对变量的写入对其他线程的可见性。

在上面的场景中,一个线程(T1)修改了counter变量的值,另一个线程(T2)读取counter变量(但是不修改它),声明counter变量为volatile足以保证对counter变量的写入对T2可见。

但是,如果T1和T2都去增加counter变量的只,name声明counter变量为volatile是不够的,后面会说明。

全volatile可见性保证

实际上,Java的volatile的可见性保证不止volatile变量本身。可见性保证如下:

如果线程A写一个volatile变量,线程B随后读取这个volatile变量,那么在写这个volatile变量之前对线程A可见的所有变量,在线程B读取这个volatile变量之后对线程B也可见。

如果线程A读取一个volatile变量,那么当A读取这个volatile变量时所有对线程A可见的变量也可以从主存中再次读取。

我用下面的代码来说明:

public class MyClass {

private int years;

private int months;

private volatile int days;

public void update(int years, int months, int days) {

this.years = years;

this.months = months;

this.days = days;

}

}

update()方法写入三个变量,只有days变量是volatile的。

全volatile可见性保证的意思是,当一个值写入到days变量,则所有对当前线程可见的变量也会都写入到主存,也就是当一个值写入到days变量,则years和months的只也被写入到主存。

当读取years,months和days的值,可以这样做:

public class MyClass {

private int years;

private int months;

private volatile int days;

public int totalDays() {

int total = this.days;

total += months * 30;

total += years * 365;

return total;

}

public void update(int years, int months, int days) {

this.years = years;

this.months = months;

this.days = days;

}

}

需要注意的是totalDays()方法起始于读取days的值到total变量中。当读取days的值时,months和years的值也被读取到主存。因此可以保证你看到的是days,months和years的最新的值,前提是保证上面的读取顺序。

指令重排序挑战

出于性能的考量,JVM和CPU允许对程序中的指令进行重排序,只要指令的语义不变。例如下面的指令:

int a = 1;

int b = 2;

a++;

b++;

这些指令可以按照下面的顺序重排,并不会丢失程序的语义:

int a = 1;

a++;

int b = 2;

b++;

但是,指令重排序对于其中一个变量是volatile变量这种情况是有挑战的。让我们看一下MyClass这个类:

public class MyClass {

private int years;

private int months;

private volatile int days;

public void update(int years, int months, int days) {

this.years = years;

this.months = months;

this.days = days;

}

}

一旦update()方法对days变量写入一个值,years和months新写入的只也刷入到主存,但是,如果有JVM指令重排序,像下面这样:

public void update(int years, int months, int days) {

this.days = days;

this.months = months;

this.years = years;

}

months和years的只在days变量修改的情况下依然会写入到主存,但是这时将years和days变量值刷入主存这件事发生在对months和years写入新值之前,则对years和days的更新对其他线程来说就不可见了。这下指令重排序就改变了程序的语义。

Java有一个应对此问题的解决方案,下面会讲到。

Java的volatile的Happens-Before保证

为了解决指令重排序的挑战,Java的volatile关键字除了可见性保证之外,给出了一个“happens-before”的保证。happens-before保证如下情况:

如果读取和写入其他非volatile变量发生在写入volatile变量之前(这种情况这些非volatile变量也会被刷入主存),则读取和写入这些变量不能被重排序为发生在写入这个volatile变量之后(禁止指令重排序)。在写入一个volatile变量之前的读取和写入非volatile变量被保证为“happen before”写入这个volatile变量。需要注意的是,例如在写入一个volatile变量之后读写其他变量可以被重排序到写入这个volatile变量之前。从“之后”重排序到”之前“是允许的,但是从”之前“重排序到”之后“是禁止的。

如果读写其他非volatile变量发生在读取一个volatile变量之后(这种情况这些非volatile变量也会被刷到主存),则读写这些变量不能被重排序为发生在读取这个volatile变量之前。需要注意的是,读取其他变量发生在读取一个volatile变量之前能够被重排序为发生在读取这个volatile变量之后。从”之前“重排序到“之后”是允许的,但是从“之后”重排序到“之前”是被禁止的。

上面的happens-before保障保证的volatile关键字的可见性是强制的。

volatile不总是足够的

尽管volatile关键字保证了所有对一个volatile变量的读取都是从主存中读取,所有对volatile关键字的写入都是直接到主存,但是仍有其他情况使得声明一个变量为volatile是不足够的。

在前面解释的情况,也就是只有Thread1写共享变量counter,声明counter变量为volatile足以保证Thread2总是看到最新写入的值。

实际上,多线程都可以写一个共享的volatile变量,并且仍然在主存中存储正确的值,前提是写入变量的新值不依赖于它之前的值。也就是说,如果一个线程写入一个值到共享的volatile变量不需要先去读它的值去产出下一个值。

只要一个线程需要首先读取一个volatile变量的值,基于这个值生成一个新值,则一个volatile关键字不足以保证正确的可见性。在读取volatile变量然后写入新值的短暂的间隙,会产生竞态条件(race condition),这时多个线程可能读取到相同的volatile变量的值,生成这个变量的新值,当将新值写回主存时,会覆盖彼此的值。

多线程增加相同计数器的值就是这种情况,导致一个volatile声明不足够。下面详细解释这种情况。

想象如果Thread1读取一个值为0的共享的counter变量到它的CPU缓存,增加1并且不将这个改变的值写回主存。Thread2然后从主存中读取相同的值仍为0counter变量到它的CPU缓存。Thread2也为它增加1,也不写回主存。这种情况如下图所示:

b08668b7a49cb5e1d7e0b91f06b3ee17.png

Thread1和Thread2此时实际上已经不同步了。共享变量counter的值应该为2,但是每个线程在CPU缓存中的这个变量的值都为1,在主存中的值仍为0,这就乱了!尽管这两个线程最终会将值写回主存中的共享变量,这个值也是不正确的。

何时volatile是足够的?

正如前面所说,如果两个线程都去读写同一个共享变量,只对这个共享变量使用volatile关键字是不够的。你需要使用一个synchronized关键字去保证读写相同变量是原子的。读写一个volatile变量不会阻塞线程的读写。

作为synchronized块替代方法,你可以使用java.util.concurrent包中的众多原子数据类型。比如,AtomicLong或者AtomicReference或其他的类型。

只有一个线程读写一个volatile变量值,其他线程只读取变量,则这些读线程能够保证看到写入这个volatile变量的最新值,如果不声明为volatile,则这种情况不能保证。

volatile的性能考量

读写volatile变量会导致变量被读写到主存。读写主存比访问CPU缓存开销更大。访问volatile变量也会禁止指令重排序,而指令重排序是一个正正常的性能优化技术。因此,你应该只在真正需要保证变量可见性的时候使用volatile变量。

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

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

相关文章

java 如何发提示_消息提醒-如何实现收到待办给QQ发送提醒?

一、前期准备一台windows服务器(用于运行QQ机器人)下载QQ机器人框架二、QQ机器人配置1、解压前面下载的机器人框架和httpapi2、打开解压后的 小栗子框架.exe3、在用户列表右击选择添加单个4、添加后右击点添加后的账号选择登录选中QQ5、点击应用中心>点击导入插件>选中前…

顶级程序员的心得 - Coders at Work

这篇博客经历了 CSDN 的历次改版后,格式已经坏了, 我重新写了一篇,重排了格式: 顶级程序员的心得 –– Coders at Work_SoftwareTeacher的专栏-CSDN博客顶级程序员的心得 –– Coders at Work我2009年读了 “Coders at Work”, …

敏捷软件开发宣言–Manifesto for Agile Software Development

敏捷开发, 谁不会呀, 不就是 没文档, 出活快, 用户说啥都能改? 下面是一个笑话, 王屋村的大牛说 - 我最近转手接了一个活, 完事能挣四五万, 我拿过图纸一看, 不就是盖一烟囱吗? 我们是敏捷 (Agile) 的团队,要文档作甚? 马上开始干活! 都快盖好了&am…

教师管理系统设计报告java_教师办公管理系统的设计与实现

随着计算机及网络技术的飞速发展,Internet/Intranet应用在全球范围内日益普及,当今社会正快速向信息化社会前进,信息自动化的作用也越来越大。从而使我们从繁杂的事务中解放出来,提高了我们的工作效率。教师办公管理系统是一个教育…

顶级程序员的心得–Coders at Work

最新版本在这里: (2584条消息) 顶级程序员的心得 –– Coders at Work_SoftwareTeacher的专栏-CSDN博客https://blog.csdn.net/SoftwareTeacher/article/details/113489014

scrum–yesterday once more

在敏捷开发的 SCRUM 流程中, 一个基本要求就是团队中的成员在每日例会中介绍自己昨天的进度, 今天的计划, 和遇到的困难。 下面是《现代软件工程》课程上一个学生团队在2/18 和 2/19 这两天的报告。 粗粗看去, 不禁有 “昨日重来” 的感觉。 一些同学的任务在2/18 报告的 yest…

java 类加载生命周期_Java类的加载与生命周期

一、概要:类的生命周期从类的 加载、连接、初始化 开始,到类的 卸载结束;二、几个阶段:加载:查找并加载类的二进制数据。(把类的.class文件的二进制数据读入内存,存放在运行时数据区的方法区;类…

软件工程 动物世界

在一个神奇的国度里生活着许多动物, 其中有猪, 鸡, 和鹦鹉。 它们每天搞头脑风暴, 琢磨如何创业, 最后鹦鹉提议它们合伙开一个早餐店: 具体分工如下: 猪: 提供猪肉, 做熏猪肉 (bacon) 鸡: 提供鸡蛋, 做煎蛋 鹦鹉: 提供咨询, 它会每天阅读大量博客, 给其他团队成员提供建议, 例…

java filterinputstream_java.io.FilterInputStream.close()

全屏java.io.FilterInputStream.close()方法关闭此输入流并释放与该流关联的所有系统资源。声明以下是public void close() 方法的声明:public void close()参数NA返回值该方法不返回任何值。异常IOException -- 如果发生I/ O错误。例子下面的例子显示了public void…

软件工程 敏捷的酒后问答

来源:《构建之法》 王屋村移山公司的程序员果冻最近请假参加了一系列敏捷的培训, 有好事者传言他和 “a-girl”勾搭上了, 其他年轻同事有点坐不住了, 也表示要参加此类活动。 几天后, 果冻回到公司, 给所有人发了一枚写有 “Agile” 的胸章。 他纠正大家的发音, 这个…

现代软件工程 怎么教好课 (读书笔记)

0. 教师教学有培训和参考书么? 我从来没想到过我会在大学里教书, 而且还教了好几年, 好几个学校。 当时接到任务的时候, 我把它当作实习生培训和新员工培训的”学院版”, 还是继续强调实践, 反馈, 合作, 就这么开讲了。 在微软公司, 做大部分和人相关的事情, 都得先有一个培训…

php oci 11g.dll下载,Oracle oci.dll

Oracle oci.dll是在32位PLSQL Developer软件访问Oracle 11g 64位数据库必须的系统dll文件,用于支持连接访问数据,如果系统出现相关dll文件缺失,就需重新下载,有需要的朋友快来巴士下载网下载吧!Oracle 11g oci.dll文件…

软件工程 案例分析作业

现代软件工程 构建之法 作业https://bbs.csdn.net/forums/SoftwareEngineering?typeId1723 软件工程作业 案例分析 (建议作为个人或结对作业) 很多同学有疑惑: 软件工程课是否就是枯燥的理论课? 或者是几个牛人拼命写代码,其他人抱大腿的…

php的数据模型包括,数据库中模型的分类有哪些

数据库中模型的分类:1、概念数据模型,面向用户、面向现实世界的数据模型,描述一个单位的概念化结构;2、逻辑数据模型,可以通过实体和关系勾勒出企业的数据蓝图;3、物理数据模型,具有以实物或画图…

php 万分之一几率,那万分之一的概率啊……

【今日】不要心存侥幸心理,那万分之一的概率啊……也许就是发生在自己身上。【正文】1老公抱着暖暖,大踏步的向前走去,兴奋的说:“我刚在群里问了一下,还没人回复,你说不会两家店全部装修吧?这万分之一的概…

JS常用事件大全

事件 事件通常与函数配合使用,这样就可以通过发生的事件来驱动函数执行。 注意:事件名称大小写敏感。若是事件监听方式,则在事件名的前面取消on。 1. 鼠标事件 给btn按钮添加点击事件,点击弹出 你好! 2. 键盘事件…

php3绕过,PHPB2B注入#3(绕过过滤)

PHPB2B某处注入绕过过滤。官方最新版本. https://github.com/ulinke/phpb2b/archive/master.zip详细说明:1.注册企业会员。2.注册企业会员且通过审核。发布产品。漏洞文件。virtual-office/product.phpContent-Disposition: form-data; name"data[product][sor…

java五子棋实训训心得,java五子棋实习报告

java五子棋实习报告 Java 程序设计基础 实习报告 课程名称 Java 程序设计基础 实习题目 java 五子棋 专 业 班 级 学 号 学生姓名 指导教师 Java 实习报告 一、一、J JAVAAVA 技术介绍技术介绍 Java 技术是一门编程语言,也是一个平台,它基于 Java 虚拟机…

java如何实现开机启动,怎样实现开机启动holer

Holer客户端软件设置开机启动Holer client 1.2版本支持设置开机自启动。Holer Java语言版本解压软件包,进入目录:cd holer-client/binWindows系统:双击 setup.vbs注意事项:请确保当前用户对如下目录具有读取、写入、执行、修改等权…

《我和他的结婚录像和相册集》的快速传播

当你看到你的一个朋友的 Live Space 有了这个更新,你当然想看. 但是你是点击文字 “我和他的结婚录像和相册集”, 还是旁边的 "心形图标"? 很多人,包括我,都点击了"心形图标", 不幸的是,这样…