通过示例了解挥发

未来的波动 我们已经花了几个月的时间来稳定Plumbr中的锁定检测功能 。 在此期间,我们遇到了许多棘手的并发问题。 许多问题是独特的,但是一种特殊类型的问题一直反复出现。

您可能已经猜到了–滥用volatile关键字。 我们已经发现并解决了许多问题,其中大量使用volatile使应用程序的任意部分变慢,延长了锁定保持时间,并最终使JVM屈服。 反之亦然-授予过于宽松的访问策略会引发一些令人讨厌的并发问题。

我想每个Java开发人员都会回想起该语言的第一步。 使用手册和教程的日子日复一日。 这些教程都有关键字列表,其中volatile是最可怕的关键字之一。 随着时间的流逝,越来越多的代码不需要使用此关键字,我们很多人都忘记了volatile的存在。 直到生产系统开始以无法预测的方式破坏数据或死亡。 调试这种情况迫使我们中的一些人真正理解了这个概念。 但是我敢打赌,这并不是一个令人愉快的课程,所以也许我可以通过一个简单的例子来阐明一些概念,从而节省一些时间。

挥发作用的例子

该示例模拟了一个银行办公室。 银行办公室的类型,您可以在此从售票机中选择队列编号,然后在您前面的队列得到处理后等待邀请。 为了模拟这样的办公室,我们创建了以下示例,该示例由两个线程组成。

这两个线程中的第一个被实现为CustomerInLine。 这是一个线程,除了等待NEXT_IN_LINE中的值与客户的票证匹配外,什么也不做。 票号被硬编码为#4。 时间到了( NEXT_IN_LINE> = 4),线程将宣布等待结束并结束。 这模拟了一个有一些客户在排队的到达办公室的客户。

排队实现在Queue类中,该类运行一个循环,该循环调用下一个客户,然后通过为每个客户休眠200ms来模拟与该客户的工作。 呼叫下一个客户后,存储在类变量NEXT_IN_LINE中的值将增加1。

public class Volatility {static int NEXT_IN_LINE = 0;public static void main(String[] args) throws Exception {new CustomerInLine().start();new Queue().start();}static class CustomerInLine extends Thread {@Overridepublic void run() {while (true) {if (NEXT_IN_LINE >= 4) {break;}}System.out.format("Great, finally #%d was called, now it is my turn\n",NEXT_IN_LINE);}}static class Queue extends Thread {@Overridepublic void run() {while (NEXT_IN_LINE < 11) {System.out.format("Calling for the customer #%d\n", NEXT_IN_LINE++);try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}}}
}

因此,运行此简单程序时,您可能希望该程序的输出类似于以下内容:

Calling for the customer #1
Calling for the customer #2
Calling for the customer #3
Calling for the customer #4
Great, finally #4 was called, now it is my turn
Calling for the customer #5
Calling for the customer #6
Calling for the customer #7
Calling for the customer #8
Calling for the customer #9
Calling for the customer #10

看来,这个假设是错误的。 取而代之的是,您将看到通过10个客户的列表进行的队列处理,并且不幸的线程模拟了#4客户,从不对其看到邀请发出警报。 发生了什么,为什么客户仍然坐在那里无休止地等待呢?

分析结果

您在这里面临的是将JIT优化应用于代码,该代码将对NEXT_IN_LINE变量的访问进行缓存。 两个线程都有自己的本地副本,并且CustomerInLine线程从不看到Queue实际增加了线程的值。 如果现在您认为这是JVM中的一种可怕的错误,那么您就不完全正确了–允许编译器这样做以避免每次都重新读取该值。 因此,您可以提高性能,但要付出代价-如果其他线程更改状态,则缓存副本的线程将不知道该状态,并使用过时的值进行操作。

对于volatile正是这种情况。 使用此关键字,编译器将被警告特定状态是易失性的,并且每次执行循环时,代码都被强制重新读取该值。 有了这些知识,我们就可以进行简单的修复-只需将NEXT_IN_LINE的声明更改为以下内容,您的客户就不会永远坐在队列中:

static volatile int NEXT_IN_LINE = 0;

对于那些只了解volatile用例而感到满意的人,您将很高兴。 只是要注意附加的成本–当您开始声明所有内容都是易失性时,您正在迫使CPU忽略本地缓存并直接进入主内存,从而减慢了代码的速度并阻塞了内存总线。

引擎盖下易挥发

对于那些希望详细了解该问题的人,请和我在一起。 要查看底层发生了什么,请打开调试以查看JIT从字节码生成的汇编代码。 通过指定以下JVM选项可以实现此目的:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

在启用和禁用volatile的情况下启用这些选项的情况下运行程序,可以为我们提供以下重要见解:

运行不带volatile关键字的代码,表明我们在指令0x00000001085c1c5a上可以比较两个值。 当比较失败时,我们继续从0x00000001085c1c60到0x00000001085c1c66,后者跳回到0x00000001085c1c60,并产生无限循环。

0x00000001085c1c56: mov    0x70(%r10),%r11d0x00000001085c1c5a: cmp    $0x4,%r11d0x00000001085c1c5e: jge    0x00000001085c1c68  ; OopMap{off=64};*if_icmplt; - Volatility$CustomerInLine::run@4 (line 14)0x00000001085c1c60: test   %eax,-0x1c6ac66(%rip)        # 0x0000000106957000;*if_icmplt; - Volatility$CustomerInLine::run@4 (line 14);   {poll}0x00000001085c1c66: jmp    0x00000001085c1c60  ;*getstatic NEXT_IN_LINE; - Volatility$CustomerInLine::run@0 (line 14)0x00000001085c1c68: mov    $0xffffff86,%esi

使用volatile关键字后,我们可以看到在指令0x000000010a5c1c40上我们将值加载到寄存器中,在0x000000010a5c1c4a上将其与保护值4进行了比较。如果比较失败,我们从0x000000010a5c1c4e跳回到0x000000010a5c1c40,再次为新的值加载值校验。 这样可以确保我们看到NEXT_IN_LINE变量的值已更改

0x000000010a5c1c36: data32 nopw 0x0(%rax,%rax,1)0x000000010a5c1c40: mov    0x70(%r10),%r8d    ; OopMap{r10=Oop off=68};*if_icmplt; - Volatility$CustomerInLine::run@4 (line 14)0x000000010a5c1c44: test   %eax,-0x1c1cc4a(%rip)        # 0x00000001089a5000;   {poll}0x000000010a5c1c4a: cmp    $0x4,%r8d0x000000010a5c1c4e: jl     0x000000010a5c1c40  ;*if_icmplt; - Volatility$CustomerInLine::run@4 (line 14)0x000000010a5c1c50: mov    $0x15,%esi

现在,希望这种解释可以使您摆脱几个讨厌的错误。

翻译自: https://www.javacodegeeks.com/2014/08/understanding-volatile-via-example.html

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

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

相关文章

jQuery设计思想

前面的话 在深入了解jQuery的各个细节之前&#xff0c;需要对jQuery的设计思想有一个大致的了解。在遇到问题时&#xff0c; 知道应该使用jQuery的哪一个功能&#xff0c;然后迅速从手册中找到具体的用法。本文将详细介绍jQuery的设计思想 选择元素 jQuery的基本设计思想和主要…

sql性能优化二

1、IN 操作符 用IN写出来的SQL的优点是比较容易写及清晰易懂&#xff0c;这比较适合现代软件开发的风格。但是用IN的SQL性能总是比较低的&#xff0c;从Oracle执行的步骤来分析用IN的SQL与不用IN的SQL有以下区别&#xff1a; ORACLE试图将其转换成多个表的连接&#xff0c;如果…

cf1039D 分块

cf1039D 链接 cf 思路 一次k可以贪心O(n)算。 对于\(≤\sqrt{n}\)的k&#xff0c;暴力算。 对于\(&#xff1e;\sqrt{n}\)的k&#xff0c;最多会有\(\sqrt{n}\)种答案&#xff0c;而且答案单调。 二分就行了。 复杂度\(O(nlognn\sqrt{n}logn)\) 递归会被卡&#xff0c;所以要记…

Source Insight 常用设置和快捷键大全

摘自&#xff1a;https://www.cnblogs.com/bluestorm/archive/2012/10/28/2743792.html Source Insight 4.0 文件类型、编码格式、tab转空格、tab键自动补全设置。。。http://www.cnblogs.com/bluestorm/p/6864540.html 1.括号配对高亮&#xff1a;“在前括号左侧&#xff0c;后…

JUnit中的参数化测试运行器

我们都有书面的单元测试&#xff0c;其中在一个测试中针对不同的可能的输入输出组合进行测试。 让我们以一个简单的斐波那契数列为例来看看它是如何完成的。 以下代码针对提到的元素数量计算斐波那契数列&#xff1a; import java.math.BigInteger; import java.util.ArrayLi…

HDU 1212 Big Number

题意&#xff1a;给一数字字符串s ( ns.size()<1000 ) 和数字m (<1e5) 求s%m 模拟除法&#xff0c; k初值0&#xff0c;按s[0]...累乘相加&#xff0c;把字符串还原成数字&#xff0c;比m大时-m&#xff0c;继续按位还原到s[n-1] 此时剩下的k再%m即为所求 #include<…

BOM之navigator对象和用户代理检测

前面的话 navigator对象现在已经成为识别客户端浏览器的事实标准&#xff0c;navigator对象是所有支持javascript的浏览器所共有的。本文将详细介绍navigator对象和用户代理检测 属性 与其他BOM对象的情况一样&#xff0c;每个浏览器中的navigator对象也都有一套自己的属性。下…

CF888G XOR-MST 最小异或生成树

CF888G XOR-MST 链接 CF888G 思路 trie上贪心&#xff0c;先左右两边连边&#xff0c;再用一条边的代价连起左右两颗树。因为内部的边一定比跨两棵树的边权笑&#xff0c;显然是对的。 代码自己瞎yy的。启发式合并 代码 #include <bits/stdc.h> #define ll long long usi…

页面事件的控制

1.设置默认焦点ASP.NET1.1中设置方法&#xff1a;<body onload"document.getElementById(TextBox1).focus();"><body onkeydown"document.all(TextBox1).focus();">ASP.NET2.0中设置方法&#xff1a;<form id"Form1"method"…

简而言之,JUnit:测试结构

尽管存在关于JUnit测试的书籍和文章&#xff0c;但我仍然经常遇到程序员&#xff0c;他们至多对这个工具及其正确用法都不甚了解。 因此&#xff0c;我想到了编写多部分教程的想法&#xff0c;从我的角度解释了要点。 也许在这个小型系列中采用的动手方法可能适合使一两个额外…

cf1207解题报告

cf1207解题报告 A 模拟 #include <bits/stdc.h> #define ll long long using namespace std; ll T,a,b,c,x,y; int main() {cin>>T;while(T --> 0) {cin>>a>>b>>c>>x>>y;ll ans0;if(x>y) {while(a>2&&b>1) ansx…

Oracle 用脚本安装第二个数据库

安装第二个数据库&#xff1a;登录oracle用户进入家目录&#xff0c;添加配置环境变量&#xff1a;vi .bash_profier ORACLE_SIDprod2临时环境变量&#xff1a;$export ORACLE_HOME/u01/app/oracle/product/11.2.0/db_1 $export ORACLE_SIDprod2创建第二个数据库文件目录&#…

深入学习jQuery鼠标事件

前面的话 鼠标事件是DOM事件中最常用的事件&#xff0c;jQuery对鼠标事件进行了封装和扩展。本文将详细介绍jQuery鼠标事件 类型 鼠标事件共10类&#xff0c;包括click、contextmenu、dblclick、mousedown、mouseup、mousemove、mouseover、mouseout、mouseenter和mouseleave c…

智能自动PPR更改事件策略

ADF开发人员普遍认为&#xff0c;将迭代器绑定更改事件策略设置为ppr在性能方面不是一件好事&#xff0c;因为此策略会强制框架刷新每个请求上绑定到此迭代器的所有属性绑定。 这不是真的&#xff01; 框架仅刷新在请求期间已更改的属性和依赖于已更改属性的属性。 让我们考虑…

[转]国际化: 理解Java平台上的Locale

From:http://jatula.javaeye.com/blog/183680 语言和地理环境对我们的文化产生重要影响.我们同他人之间的交流以及生活中的事件都发生在语言和地理环境所产生的一个系统里.由于语言和环境的不同,以至 需要我们来制定一个适合的方式来达到向他人表述我们自己或者我们的想法的目的…

深入学习jQuery描述文本内容的3个方法

前面的话 在javascript中&#xff0c;描述元素内容有5个属性&#xff0c;分别是innerHTML、outerHTML、innerText、outerText和textContent。这5个属性各自有各自的功能&#xff0c;且兼容性不同。jQuery针对这样的处理提供了3个便捷的方法&#xff0c;分别是&#xff1a;html(…

luoguP4551最长异或路径

P4551最长异或路径 链接 luogu 思路 从\(1\)开始\(dfs\)求出\(xor\)路径。然后根据性质\(x\)到\(y\)的\(xor\)路径就是\(xo[x]^xo[y]\) 代码 #include <bits/stdc.h> using namespace std; const int _1e57; int xo[_],w[_],ans-1,num0; struct node {int v,q,nxt; }e[_&…

谈一谈我的996 (随笔)

说一说996这个最近技术圈比较热门的话题。 什么是996&#xff0c;早九晚九一周上班6天。 看朋友圈&#xff0c;有个朋友说自己没有经历过什么是996&#xff0c;感觉自己是个假的程序员&#xff0c;是不是程序员就应该加班呢&#xff0c;是不是已经下意识&#xff0c;大众性的认…

装饰器设计模式的应用

嗨&#xff0c;您好&#xff01; 今天&#xff0c;我将展示装饰设计模式的实际应用。 装饰器设计模式是一种广泛使用的设计模式&#xff0c;同时在运行期间处理图形&#xff0c;树木和动态更改。 如果您正在寻找或尝试进行递归&#xff0c;这也是一个不错的选择。 我喜欢它。…

ubuntu postgresql 的安装

本人亲测&#xff0c;在ubuntu9.10上安装的postgresql 8.3版本。郁闷了好几天&#xff0c;终于ok了。sudo apt-get install postgresql-8.3 postgresql-client-8.3 postgresql-contrib-8.3然后在/etc/profile里加上export POSTGRES_HOME/usr/lib/postgresql/8.3export PGLIB$PO…