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 ...
随机推荐
- Bootstrap_表单_图标
在Bootstrap框架中是通过给元素添加“glyphicon”类名来实现,然后通过伪元素“:before”的“content”属性调取对应的icon编码: <span class=" ...
- 基于Jquery+Ajax+Json实现分页显示
1.后台action产生json数据. List blackList = blackService.getBlackInfoList(mobileNum, gatewayid, startDate, ...
- 如何在Android Studio中使用Gradle发布项目至Jcenter仓库
简述 目前非常流行将开源库上传至Jcenter仓库中,使用起来非常方便且易于维护,特别是在Android Studio环境中,只需几步配置就可以轻松实现上传和发布. Library的转换和引用 博主的 ...
- 项目任务管理(TaskMgr)设计篇
为什么使用void FilllXX(TypeA pParm1, TypeB pParm2) 应用场景色:void FillXX的好处是可以不用关心实例情况:如果在方法体中需要一个实例,而方法体只知道基 ...
- qt实现头像上传功能(朝十晚八的博客,一堆帖子)
http://www.cnblogs.com/swarmbees/p/5688885.html
- 使用CSS为图片添加边框的几种方法
css的应用十分广泛,即便用在图片的效果中也是方法多样,本文下面就介绍五种为图片添加特殊效果边框的CSS写法阴影效果 通过使用带有一些padding之的背景图来添加阴影效果. HTML <img ...
- MySql的大小写问题
原来Linux下的MySQL默认是区分表名大小写的,通过如下设置,可以让MySQL不区分表名大小写:1.用root登录,修改 /etc/my.cnf:2.在[mysqld]节点下,加入一行: lowe ...
- bzoj1978
朴素的算法是O(n2logn)观察这个算法,似乎很难在进行优化我们就要换一种思路考虑到一个数的约数总不是很多,穷举约数也是可以在O(sqrt(x))的时间内完成的并且注意到,能否继续往下选数,只在于最 ...
- Linux Shell编程(5)——shell特殊字符(下)
{}代码块[花括号]. 这个结构也是一组命令代码块,事实上,它是匿名的函数.然而与一个函数所不同的,在代码块里的变量仍然能被脚本后面的代码访问. bash$ { local a; a=123 ...
- tyvj P1517 飘飘乎居士的乌龟(最大流)
P1517 飘飘乎居士的乌龟 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 飘飘乎居士养了乌龟.当然,这些乌龟是用来出售赚取利润的. 描述 飘飘乎居士的乌龟 ...