UNDERSTANDING VOLATILE VIA EXAMPLE--reference
We have spent last couple of months stabilizing the lock detection functionality in Plumbr. During this we have stumbled into many tricky concurrency issues. Many of the issues are unique, but one particular type of issues keeps repeatedly appearing.
You might have guessed it – misuse of thevolatile keyword. We have detected and solved bunch of issues where the extensive usage of volatile made arbitrary parts of the application slower, extended locks holding time and eventually bringing the JVM to its knees. Or vice versa – granting too liberal access policy has triggered some nasty concurrency issues.
I guess every Java developer recalls the first steps in the language. Days and days spent with manuals and tutorials. Those tutorials all had the list of keywords, among which the volatilewas one of the scariest. As days passed and more and more code was written without the need for this keyword, many of us forgot the existence of volatile. Until the production systems started either corrupting data or dying in unpredictable manner. Debugging such cases forced some of us to actually understand the concept. But I bet it was not a pleasant lesson to have, so maybe I can save some of you some time by shedding light upon the concept via a simple example.
Example of volatile in action
The example is simulating a bank office. The type of bank office where you pick a queue number from a ticketing machine and then wait for the invite when the queue in front of you has been processed. To simulate such office, we have created the following example, consisting of two threads.
First of the two threads is implemented as CustomerInLine. This is a thread doing nothing but waiting until the value in NEXT_IN_LINE matches customer’s ticket. Ticket number is hardcoded to be #4. When the time arrives (NEXT_IN_LINE>=4), the thread announces that the waiting is over and finishes. This simulates a customer arriving to the office with some customers already in queue.
The queuing implementation is in Queue class which runs a loop calling for the next customer and then simulating work with the customer by sleeping 200ms for each customer. After calling the next customer, the value stored in class variable NEXT_IN_LINE is increased by one.
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 {
@Override
public 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 {
@Override
public 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();
}
}
}
}
}
So, when running this simple program you might expect the output of the program being similar to the following:
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
As it appears, the assumption is wrong. Instead, you will see the Queue processing through the list of 10 customers and the hapless thread simulating customer #4 never alerts it has seen the invite. What happened and why is the customer still sitting there waiting endlessly?
Analyzing the outcome
What you are facing here is a JIT optimization applied to the code caching the access to theNEXT_IN_LINE variable. Both threads get their own local copy and the CustomerInLine thread never sees the Queue actually increasing the value of the thread. If you now think this is some kind of horrible bug in the JVM then you are not fully correct - compilers are allowed to do this to avoid rereading the value each time. So you gain a performance boost, but at a cost - if other threads change the state, the thread caching the copy does not know it and operates using the outdated value.
This is precisely the case for volatile. With this keyword in place, the compiler is warned that a particular state is volatile and the code is forced to reread the value each time when the loop is executed. Equipped with this knowledge, we have a simple fix in place - just change the declaration of the NEXT_IN_LINE to the following and your customers will not be left sitting in queue forever:
static volatile int NEXT_IN_LINE = 0;
For those, who are happy with just understanding the use case for volatile, you are good to go. Just be aware of the extra cost attached - when you start declaring everything to be volatile you are forcing the CPU to forget about local caches and to go straight into main memory, slowing down your code and clogging the memory bus.
Volatile under the hood
For those who wish to understand the issue in more details, stay with me. To see what is happening underneath, lets turn on the debugging to see the assembly code generated from the bytecode by the JIT. This is achieved by specifying the following JVM options:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
Running the program with those options turned on both with volatile turned on and off, gives us the following important insight:
Running the code without the volatile keyword, shows us that on instruction 0x00000001085c1c5a we have comparison between two values. When comparison fails we continue through 0x00000001085c1c60 to 0x00000001085c1c66 which jumps back to 0x00000001085c1c60 and an infinite loop is born.
0x00000001085c1c56: mov 0x70(%r10),%r11d
0x00000001085c1c5a: cmp $0x4,%r11d
0x00000001085c1c5e: 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
With the volatile keyword in place, we can see that on instruction 0x000000010a5c1c40 we load value to a register, on 0x000000010a5c1c4a compare it to our guard value of 4. If comparison fails, we jump back from 0x000000010a5c1c4e to 0x000000010a5c1c40, loading value again for the new check. This ensures that we will see changed value of NEXT_IN_LINE variable.
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,%r8d
0x000000010a5c1c4e: jl 0x000000010a5c1c40 ;*if_icmplt
; - Volatility$CustomerInLine::run@4 (line 14)
0x000000010a5c1c50: mov $0x15,%esi
Now, hopefully the explanation will save you from couple of nasty bugs.
原文:https://plumbr.eu/blog/understanding-volatile-via-example
UNDERSTANDING VOLATILE VIA EXAMPLE--reference的更多相关文章
- C关键字volatile总结
做嵌入式C开发的相信都使用过一个关键字volatile,特别是做底层开发的.假设一个GPIO的数据寄存器地址是0x50000004,我们一般会定义一个这样的宏: #define GDATA *((vo ...
- [转]Keyword Reference (F#)
Visual F# Development Portal http://msdn.microsoft.com/en-us/library/vstudio/ff730280.aspx 本文转自:http ...
- report for PA2
目录 说明 Report for PA 2(writed with vim) Part i - pa2.1 Steps: instr(seperately) Part ii - 2.2 Part ii ...
- 【JUC】JDK1.8源码分析之SynchronousQueue(九)
一.前言 本篇是在分析Executors源码时,发现JUC集合框架中的一个重要类没有分析,SynchronousQueue,该类在线程池中的作用是非常明显的,所以很有必要单独拿出来分析一番,这对于之后 ...
- linux内核设计模式
原文来自:http://lwn.net/Articles/336224/ 选择感兴趣内容简单翻译了下: 在内核社区一直以来的兴趣是保证质量.我们需要保证和改善质量是显而易见的.但是如何做到却不是那么简 ...
- Rpc框架dubbo-client(v2.6.3) 源码阅读(二)
接上一篇 dubbo-server 之后,再来看一下 dubbo-client 是如何工作的. dubbo提供者服务示例, 其结构是这样的!dubbo://192.168.11.6:20880/com ...
- 【Java并发编程】17、SynchronousQueue源码分析
SynchronousQueue是一种特殊的阻塞队列,不同于LinkedBlockingQueue.ArrayBlockingQueue和PriorityBlockingQueue,其内部没有任何容量 ...
- 第十一章 dubbo通信框架-netty4
netty4是2.5.6引入的,2.5.6之前的netty用的是netty3.在dubbo源码中相较于netty3,添加netty4主要仅仅改了两个类:NettyServer,NettyClient. ...
- .NET:CLR via C# Primitive Thread Synchronization Constructs
User-Mode Constructs The CLR guarantees that reads and writes to variables of the following data typ ...
随机推荐
- js对象的复制,传递,新增,删除和比较
当我们把一个某个对象拷贝或者传递给某个函数时,往往传递的是该对象的引用. 因此我们在引用上做的任何改动,都将会影响到它所引用的原对象. 复制,拷贝 var o = { add: 'Changdao ...
- 基于 libmemcahce 的memcache 操作
<?php echo '<pre>'; //测试的键值的数量 $count = 30; $mem = create_memcache(); //var_dump($mem->i ...
- Python学习 - 使用BeautifulSoup来解析网页一:基础入门
写技术博客主要就是总结和交流的,如果文章用错,请指正啊! 以前一直在使用SGMLParser,这个太费时间和精力了,现在为了毕业设计,改用BeautifulSoup来实现HTML页面的解析工作的. 一 ...
- WPF学习笔记-自定义窗口
代码部分 <Style x:Key="for_noresize_window" TargetType="{x:Type Window}"> < ...
- 应用Oracle(用户创建和授权)
使用oracle命令创建用户,并授权. 系统管理员身份登录 cmd中, sqlplus / as sysdba 若登录失败,则 sqlplus system/[数据库创建时指定密码] as sysdb ...
- mysql的乱码问题
ALTER TABLE `news`.`snapshots` MODIFY title VARCHAR() CHARACTER SET utf8; ALTER TABLE `news`.`snapsh ...
- c#语句 (随堂练习)
1. 方程ax²+bx+c=0:一元二次方程.求根 输入a,b,c的值 Δ=b²-4ac:若Δ<0方程无实根 若Δ>0,方程有两个不相同的实根x1 x2gen 若Δ ...
- 解决VS2010中产生的ipch文件夹和sdf文件
纯粹参考他人博文,转载时注明参考链接!!! 参考链接: http://blog.sina.com.cn/s/blog_707a4cbf0100r5jq.html 在VS2010开发的项目中,ipch文 ...
- HDOJ迷宫城堡(判断强连通 tarjan算法)
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission( ...
- CH Round #58 - OrzCC杯noip模拟赛day2
A:颜色问题 题目:http://ch.ezoj.tk/contest/CH%20Round%20%2358%20-%20OrzCC杯noip模拟赛day2/颜色问题 题解:算一下每个仆人到它的目的地 ...