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,一经查实,立即删除!

相关文章

现代软件工程系列 创新靠学分

如何在学校里讲创新, 这是一个老问题。 但是天朝的教育体制总有办法,请看: http://news.163.com/10/1125/03/6MABIS4H00014AED.html 武汉一高校设"创新学分" 不搞创新不能毕业 去年秋季,武汉科技大学中南分校出台《学籍管理规定》,…

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”, …

jdbc mysql myeclipse_关于JDBC连接MySQL的问题,我一直解决不了(Myeclipse 环境下)

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼package org.mlj.jdbc;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.sql.Statement;import java.net.UnknownHostException;import java.net.InetAddress.*; public class…

现代软件工程课件 需求分析 如何提出靠谱的项目建议 NABCD

《构建之法 - 现代软件工程》课件 互联网时代对于创新者来说, 既是一个伟大的时代, 又是一个糟糕的时代。 你有很多机会做出影响世界的产品, 但是, 似乎任何想法都被别人想到过了, 做出来了, 上市了, 移植到各种平台上去了… 那么我们后来人除了羡慕别人生得早, 还有什么机…

java禁止数据库写入事务_Java -- JDBC 事务处理, 事务的隔离级别 脏读 不可重复读 等......

1. 事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。数据库开启事务命令•start transaction开启事务•Rollback回滚事务•Commit提交事务JDBC控制事务语句•Connection.setAutoCommit(false); //start transa…

现代软件工程 教学计划 适应两种难度和重点

现代软件工程 教学计划 经过 2007 - 2018 年,30个不同学校的实践后, 《构建之法》在大学的两种教法已经出现了, 这两种教学计划适应于两种难度和重点。 下面我们具体介绍这两种计划。 1. 软件工程导论(适合大一下的学生,在学过…

java中setDocument_Java ActionItem.setDocumentId方法代码示例

import org.kuali.rice.kew.actionitem.ActionItem; //导入方法依赖的package包/类Test public void testConvertActionItem() throws Exception {// get test dataString testWorkgroupName "TestWorkgroup";Group testWorkgroup KimApiServiceLocator.getGroupSe…

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

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

敏捷软件开发 12 原则

作为 <现代软件工程> 的一个作业, 我要求同学们把 英文的敏捷开发原则 翻译成中文并解释。 大部分同学都提供了持续重构, 不断提高的版本。 技术翻译专家余晟老师也对其中较难翻译的三条原则提了很好的建议。 下面是我的尝试, 翻译要做到 信, 达, 雅, 很难, 而且中国的…

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

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

同学, 你的板砖呢?

这次 <现代软件工程> 的4 个团队要做下面的项目, 唯一的要求是 - 做真实的项目: 第一组: 一个BBS 的通用客户端 第二组: 挖掘学术圈内的师承关系 第三组: 真人拳皇 - 把你老板的照片变成对手, 然后给他一顿痛打 第四组: 一个叫呆呆的东东。 他们缺什么呢? 缺一些…

设置java环境变量生效 centos_解决CentOS java环境不生效的问题

查看当前java版本[rootlocalhost jdk1.6.0_45]# java -versionopenjdk version "1.8.0_65"OpenJDK Runtime Environment (build 1.8.0_65-b17)OpenJDK 64-Bit Server VM (build 25.65-b01, mixed mode)发现默认的还是系统自带的1.8&#xff0c;不是配置的1.6。[rootl…

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

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

JAVA中vector是否存在数据_如何找出std :: vector中是否存在项目?

我要做的就是检查向量中是否存在某个元素&#xff0c;因此我可以处理每种情况。if ( item_present )do_this();elsedo_that();#1楼您可以尝试以下代码&#xff1a;#include #include // You can use class, struct or primitive data type for Itemstruct Item {//Some fields}…

scrum–yesterday once more

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

软件工程 之 画扇面

软件工程的历史虽然说只有短短的四十多年时间 &#xff08;1968 年提出&#xff09;&#xff0c; 但是软件工程的主体 – 人类 – 已经出现在世界上好些年了。 人还是那些人&#xff0c;事儿还是那些事儿, 好的&#xff0c;坏的&#xff0c;可笑的模式同样会出现。 我看到同学们…

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

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

tooooooooooooold

看到一篇文章: http://blog.csdn.net/atixujie/archive/2011/04/06/6305371.aspx 推荐软件工程的书籍, 我看了一眼, 觉得特别陌生, 就找了一下原文, 原文在: http://www.cs.cmu.edu/afs/cs.cmu.edu/user/shaw/www/Edparts/sebook.htm 在原始的网页上有一段小字: It was l…

java 配置嵌套事务_Spring 事务嵌套的配置

问题在使用Spring管理Service层事务时&#xff0c;会遇到service方法事务嵌套的问题。默认情况下&#xff0c;service方法有自己独立的事务&#xff0c;如果一个复杂的service方法中&#xff0c;调用了多个事务操作&#xff0c;中间抛出异常时&#xff0c;往往不会发生事务回滚…