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的更多相关文章

  1. C关键字volatile总结

    做嵌入式C开发的相信都使用过一个关键字volatile,特别是做底层开发的.假设一个GPIO的数据寄存器地址是0x50000004,我们一般会定义一个这样的宏: #define GDATA *((vo ...

  2. [转]Keyword Reference (F#)

    Visual F# Development Portal http://msdn.microsoft.com/en-us/library/vstudio/ff730280.aspx 本文转自:http ...

  3. report for PA2

    目录 说明 Report for PA 2(writed with vim) Part i - pa2.1 Steps: instr(seperately) Part ii - 2.2 Part ii ...

  4. 【JUC】JDK1.8源码分析之SynchronousQueue(九)

    一.前言 本篇是在分析Executors源码时,发现JUC集合框架中的一个重要类没有分析,SynchronousQueue,该类在线程池中的作用是非常明显的,所以很有必要单独拿出来分析一番,这对于之后 ...

  5. linux内核设计模式

    原文来自:http://lwn.net/Articles/336224/ 选择感兴趣内容简单翻译了下: 在内核社区一直以来的兴趣是保证质量.我们需要保证和改善质量是显而易见的.但是如何做到却不是那么简 ...

  6. Rpc框架dubbo-client(v2.6.3) 源码阅读(二)

    接上一篇 dubbo-server 之后,再来看一下 dubbo-client 是如何工作的. dubbo提供者服务示例, 其结构是这样的!dubbo://192.168.11.6:20880/com ...

  7. 【Java并发编程】17、SynchronousQueue源码分析

    SynchronousQueue是一种特殊的阻塞队列,不同于LinkedBlockingQueue.ArrayBlockingQueue和PriorityBlockingQueue,其内部没有任何容量 ...

  8. 第十一章 dubbo通信框架-netty4

    netty4是2.5.6引入的,2.5.6之前的netty用的是netty3.在dubbo源码中相较于netty3,添加netty4主要仅仅改了两个类:NettyServer,NettyClient. ...

  9. .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 ...

随机推荐

  1. 基于jQuery选择器的整理集合

    jquery对象访问1.each(callback):以每个匹配的元素作为上下文来执行一个函数,return false;停止循环;return true;跳至下一个循环. 来个实例 : 代码如下: ...

  2. js获取url中的参数对象、js生成带参数的url

    // 获取url中的参数,并返回一个对象 $.getRequestData = function() { var url = location.search; //获取url中"?" ...

  3. DOM对象控制HTML无素——详解2

    节点属性 在文档对象模型 (DOM) 中,每个节点都是一个对象.DOM 节点有三个重要的属性 : 1. nodeName : 节点的名称 2. nodeValue :节点的值 3. nodeType ...

  4. 谈谈 jQuery 中的防冲突(noConflict)机制

    许多的 JS 框架类库都选择使用 $ 符号作为函数或变量名,jQuery 是其中最为典型的一个.在 jQuery 中,$ 符号只是 window.jQuery 对象的一个引用,因此即使 $ 被删除,w ...

  5. ECommon.Dapper

    ECommon.Dapper 轻量级的dapper扩展 我们都知道Dapper这个orm框架,但是我们也知道他的扩展目前没有特别好的,今天我就推荐一个轻量级的很方便使用的一个扩展叫做 ECommon. ...

  6. Maya+3dsMax三维建模

    Maya比较擅长动画,在人物和动物的行为活动方面比较擅长 而3ds Max在建筑物地理地图方面比较擅长,多应用于地理 将两者结合起来将会非常有用

  7. iOS中浅淡UIApplication单例-b

    在iOS的操作系统中 每一个程序都对应一个application单例,每一个application都对应一个Appdelegate代理,在代理中控制程序的各个状态.我们在程序中获取Applicatio ...

  8. windows phone 8学习 - 选择器

    1照相机 CameraCaptureTask cct=new CameraCaptureTask(); cct.Completed+=new EventHandler<PhotoResult&g ...

  9. Apache Maven 入门篇(下)

    第一篇文章大概的介绍了一下Apache Maven以及它的下载和安装,并且运行了一个简单的示例.那么在对maven有了一点接触后,接下去的一步是要了解maven的核心概念,这样才能在使用maven的时 ...

  10. [BZOJ 1874] [BeiJing2009 WinterCamp] 取石子游戏 【博弈论 | SG函数】

    题目链接:BZOJ - 1874 题目分析 这个是一种组合游戏,是许多单个SG游戏的和. 就是指,总的游戏由许多单个SG游戏组合而成,每个SG游戏(也就是每一堆石子)之间互不干扰,每次从所有的单个游戏 ...