java 中violate_Java中的volatile关键字及Cache更新

Volatile [ˈvɑːlətl],中文解释:反复无常的,易变的,不稳定的。

volatile的本意是告诉编译器,此变量的值是易变的,每次读写该变量的值时务必从该变量的内存地址中读取或写入,不能为了效率使用对一个“临时”变量的读写来代替对该变量的直接读写。编译器看到了volatile关键字,就一定会生成内存访问指令,每次读写该变量就一定会执行内存访问指令直接读写该变量。若是没有volatile关键字,编译器为了效率,只会在循环开始前使用读内存指令将该变量读到寄存器中,之后在循环内都是用寄存器访问指令来操作这个“临时”变量,在循环结束后再使用内存写指令将这个寄存器中的“临时”变量写回内存。在这个过程中,如果内存中的这个变量被别的因素(其他线程、中断函数、信号处理函数、DMA控制器、其他硬件设备)所改变了,就产生数据不一致的问题。

volatile关键字在用C语言编写嵌入式软件里面用得很多,不使用volatile关键字的代码比使用volatile关键字的代码效率要高一些,但就无法保证数据的一致性。

在Java中,volatile 会确保我们对于这个变量的读取和写入,都一定会同步到主内存里,而不是从 Cache 里面读取。

Case 1

public class VolatileTest {

private static volatile int COUNTER = 0;

public static void main(String[] args) {

new ChangeListener().start();

new ChangeMaker().start();

}

static class ChangeListener extends Thread {

@Override

public void run() {

int threadValue = COUNTER;

while ( threadValue < 5){

if( threadValue!= COUNTER){

System.out.println("Got Change for COUNTER : " + COUNTER + "");

threadValue= COUNTER;

}

}

}

}

static class ChangeMaker extends Thread{

@Override

public void run() {

int threadValue = COUNTER;

while (COUNTER <5){

System.out.println("Incrementing COUNTER to : " + (threadValue+1) + "");

COUNTER = ++threadValue;

try {

Thread.sleep(500);

} catch (InterruptedException e) { e.printStackTrace(); }

}

}

}

}

在这个程序里,我们先定义了一个 volatile 的 int 类型的变量,COUNTER。然后,我们分别启动了两个单独的线程,一个线程我们叫 ChangeListener。

ChangeListener 这个线程运行的任务很简单。它先取到 COUNTER 当前的值,然后一直监听着这个 COUNTER 的值。一旦 COUNTER 的值发生了变化,就把新的值通过 println 打印出来。直到 COUNTER 的值达到 5 为止。这个监听的过程,通过一个永不停歇的 while 循环的忙等待来实现。

另外一个 ChangeMaker 线程运行的任务同样很简单。它同样是取到 COUNTER 的值,在 COUNTER 小于 5 的时候,每隔 500 毫秒,就让 COUNTER 自增 1。在自增之前,通过 println 方法把自增后的值打印出来。

最后,在 main 函数里,我们分别启动这两个线程,来看一看这个程序的执行情况。

Incrementing COUNTER to : 1

Got Change for COUNTER : 1

Incrementing COUNTER to : 2

Got Change for COUNTER : 2

Incrementing COUNTER to : 3

Got Change for COUNTER : 3

Incrementing COUNTER to : 4

Got Change for COUNTER : 4

Incrementing COUNTER to : 5

Got Change for COUNTER : 5

程序的输出结果并不让人意外。ChangeMaker 函数会一次一次将 COUNTER 从 0 增加到 5。因为这个自增是每 500 毫秒一次,而 ChangeListener 去监听 COUNTER 是忙等待的,所以每一次自增都会被 ChangeListener 监听到,然后对应的结果就会被打印出来。

终极原因是:volatile保证所有数据的读和写都来自主内存。ChangeMaker 和 ChangeListener 之间,获取到的 COUNTER 值就是一样的。

Case 2

如果我们把上面的程序小小地修改一行代码,把我们定义 COUNTER 这个变量的时候,设置的 volatile 关键字给去掉,重新运行后发现:

Incrementing COUNTER to : 1

Incrementing COUNTER to : 2

Incrementing COUNTER to : 3

Incrementing COUNTER to : 4

Incrementing COUNTER to : 5

这个时候,ChangeListener 又是一个忙等待的循环,它尝试不停地获取 COUNTER 的值,这样就会从当前线程的“Cache”里面获取。于是,这个线程就没有时间从主内存里面同步更新后的 COUNTER 值。这样,它就一直卡死在 COUNTER=0 的死循环上了。

Case 3

再对程序做一个小的修改。我们不再让 ChangeListener 进行完全的忙等待,而是在 while 循环里面,Sleep 5 毫秒,看看会发生什么情况。

static class ChangeListener extends Thread {

@Override

public void run() {

int threadValue = COUNTER;

while ( threadValue < 5){

if( threadValue!= COUNTER){

System.out.println("Sleep 5ms, Got Change for COUNTER : " + COUNTER + "");

threadValue= COUNTER;

}

try {

Thread.sleep(5);

} catch (InterruptedException e) { e.printStackTrace(); }

}

}

}

运行结果:

Incrementing COUNTER to : 1

Sleep 5ms, Got Change for COUNTER : 1

Incrementing COUNTER to : 2

Sleep 5ms, Got Change for COUNTER : 2

Incrementing COUNTER to : 3

Sleep 5ms, Got Change for COUNTER : 3

Incrementing COUNTER to : 4

Sleep 5ms, Got Change for COUNTER : 4

Incrementing COUNTER to : 5

Sleep 5ms, Got Change for COUNTER : 5

解释: 虽然没有使用 volatile 关键字强制保证数据的一致性,但是短短 5ms 的 Thead.Sleep 的线程会让出CPU,线程被唤醒后才会去重新加载变量。它也就有机会把最新的数据从主内存同步到自己的高速缓存里面了。于是,ChangeListener 在下一次查看 COUNTER 值的时候,就能看到 ChangeMaker 造成的变化了。

虽然 JMM 只是 Java 虚拟机这个进程级虚拟机里的一个隔离了硬件实现的虚拟机内的抽象模型,但是这个内存模型,和计算机组成里的 CPU、高速缓存和主内存组合在一起的硬件体系非常相似。以上就是一个很好的“缓存同步”问题的示例。也就是说,如果我们的数据,在不同的线程或者 CPU 核里面去更新,因为不同的线程或 CPU 核有着各自的缓存,很有可能在 A 线程的更新,因为数据暂时不一致,在 B 线程里面是不可见的。

参考:

如果不介意,欢迎使用我的分享链接,让我赚几个G币:

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

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

相关文章

mechanize (1)

最近看的关于网络爬虫和模拟登陆的资料&#xff0c;发现有这样一个包 mechanize [mekə.naɪz]又称为机械化的意思&#xff0c;确实文如其意&#xff0c;确实有自动化的意思。 mechanize.Browser and mechanize.UserAgentBase implement the interface of urllib2.OpenerDirect…

ubuntun安装ssh,并远程链接服务器操作

SSH是一种以安全、加密方式连接远程主机或服务器的方法。SSH服务器接受从有SSH的客户机的连接&#xff0c;允许操作者象在本地一样地登录系统。你可以用SSH从远程运行shell和X程序。 &#xff08;1&#xff09;安装SSH服务器 加入Universe和Multiverse源后&#xff0c;用新…

java微信web支付开发_微信支付java开发详细第三方支付功能开发之支付宝web端支...

这段时间把支付基本搞完了&#xff0c;因为做的过程中遇到许多问题&#xff0c;特地记录下来&#xff0c;同时方便其他java coder&#xff0c;废话少说&#xff0c;下面开始。整体思路&#xff1a;在后台&#xff0c;根据参数创建支付宝客户端AlipayClient&#xff0c;发送参数…

Android 监控网络状态

Html代码public static boolean isNetworkAvailable(Context context) { ConnectivityManager connectivity (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivity null) { Log.i("NetWorkS…

Tokyo Tyrant安装和配置

Tokyo Tyrant安装和配置Tokyo Cabinet是日本人开发的一款DBM数据库&#xff0c;读写速度非常快。Tokyo Tyrant也是由同一作者开发的Tokyo Cabinet网络接口&#xff0c;兼容memcached协议&#xff0c;也可以通过http协议进行数据交换。Tokyo Tyrant加上Tokyo Cabinet构成一款支持…

mysql 最值复杂查询_MySQL高级查询

我们使用SQL查询不能只使用很简单、最基础的SELECT语句查询。如果想从多个表查询比较复杂的信息&#xff0c;就会使用高级查询实现。常见的高级查询包括多表连接查询、内连接查询、外连接查询与组合查询等&#xff0c;今天我们先来学习最常用、面试也很容易被问到的连接查询。我…

java事件类_关于Java事件类的一些思考

第一条是关于添加监听类时&#xff0c;如JButton button newJButton();button.addActionListener(this);如果进行两次注册监听类如再加一条button.addActionListener(this);那么当点击一次button时&#xff0c;button实际上会返回两次结果&#xff0c;相当于点击了两次button。…

java对象和json对象之间互相转换

2019独角兽企业重金招聘Python工程师标准>>> import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;import java.util.List;import net.sf.json.JSONArray;import net.sf.json.JSONObject;public class MainClass { public static…

HDU 3397 线段树 双懒惰标记

这个是去年遗留历史问题&#xff0c;之前思路混乱&#xff0c;搞了好多发都是WA&#xff0c;就没做了 自从上次做了大白书上那个双重懒惰标记的题目&#xff0c;做这个就思路很清晰了 跟上次大白上那个差不多&#xff0c;这个也是有一个sets标记&#xff0c;代表这个区间全部置…

mysql接口可以重载吗_php 到底可不可以重载

展开全部php 作为一种弱类型语言&#xff0c;本身不能像强类型如java &#xff0c;c那样&#xff0c;直接的实现重载。不过可e68a84e8a2ad62616964757a686964616f31333337393539以通过一些方法&#xff0c;间接的实现重载。使用一个统一的函数来实现重载。该方法要使用func_get…

SQL Server :理解数据记录结构

原文:SQL Server &#xff1a;理解数据记录结构在SQL Server &#xff1a;理解数据页结构我们提到每条记录都有7 bytes的系统行开销&#xff0c;那这个7 bytes行开销到底是一个什么样的结构&#xff0c;我们一起来看下。 数据记录存储我们具体的数据&#xff0c;换句话说&#…

京东云擎提供了免费的wordpress一键安装功能了

1. 京东云擎(http://jae.jd.com)提供了免费的个人博客WordPress一键安装功能了&#xff0c;如下图&#xff0c;给开发者分享福利&#xff01; 免费的应用&#xff0c;提供了源码&#xff0c;提供了数据库&#xff1a; 我之前把文章发到首页&#xff0c;遭到了封杀&#xff01;本…

java 对象加密_java.security包实现对象加密

Java原生支持常见的加密算法&#xff0c;例如DES、RSA。随便写点关于Java安全包的东西。Java.security.Provider对象官方的解释是&#xff1a;实现了 Java 安全性的一部分或者全部。provider 可能实现的服务包括&#xff1a;算法(如 DSA、RSA、MD5 或 SHA-1)&#xff0c;密钥的…

ajax请求模拟登录

前台 if (Session["username"] ! null){<div class"login"><span style"width:155px;height:85px;display:inline-block;margin-left:50px;margin-top:25px;text-align:center">(Session["username"]) 您好&#xff01;&…

Distinct源码分析

以前比较两个List数据&#xff0c;筛选出所需要的数据时候&#xff0c;一直套两层for循环来执行。用到去重(Distinct)的时候&#xff0c;这两个需求其实都是一样的&#xff0c;都是需要比较两个集合&#xff0c;查看了下它的源码&#xff0c;里面确实有值得借鉴的地方。 先附上…

python3图片转代码_python3图片转换二进制存入mysql示例代码

python3图片转换二进制存入mysql示例代码发布于 2014-09-29 18:00:01 | 198 次阅读 | 评论: 0 | 来源: 网友投递Python编程语言Python 是一种面向对象、解释型计算机程序设计语言&#xff0c;由Guido van Rossum于1989年底发明&#xff0c;第一个公开发行版发行于1991年。Pytho…

oracle面试题[关于case when的用法]

表中有A B C三列,用SQL语句实现&#xff1a;当A列大于B列时选择A列否则选择B列&#xff0c;当B列大于C列时选择B列否则选择C列declare v_sal number:2000; v_tax number; begin case when v_salv_tax:v_sal*0.03; when v_salv_tax:v_sal*0.04; when v_salv_tax:v_sal*0.05; whe…

Javascript面向对象研究心得

这段时间正好公司项目须要&#xff0c;须要改动fullcalendar日历插件&#xff0c;有机会深入插件源代码。正好利用这个机会&#xff0c;我也大致学习了下面JS的面向对象编程&#xff0c;感觉收获还是比較多的。 所以写了以下这篇文章希望跟大家探讨探讨JS的面向对象&#xff0c…

矩阵连乘积 ZOJ 1276 Optimal Array Multiplication Sequence

题目传送门 1 /*2 题意&#xff1a;加上适当的括号&#xff0c;改变计算顺序使得总的计算次数最少3 矩阵连乘积问题&#xff0c;DP解决&#xff1a;状态转移方程&#xff1a;4 dp[i][j] min (dp[i][k] dp[k1][j] p[i-1] * p[k] * p[j]) (i<k<j)5 s…

md5加密java实现_MD5加密(java实现)

java实现MD5加密:import java.security.MessageDigest;import sun.misc.BASE64Encoder;public class Tools {/** md5加密算法* return:结果为16进制的字符串长度为32位*/public static String getMd5String(String str) throws Exception{StringBuilder md5Code new StringBui…