Open shuvigoss opened 4 years ago
int a = 0;
int b = 0;
/**
* <pre>
* 第一个操作
* Normal Load
* Normal Store
*
* 第二个操作
* Normal Load
* Normal Store
*
* 结论:可以重排
* </pre>
*/
public void NormalLoad_NormalStore$$NormalLoad_NormalStore() {
a = 1;
b = 1;
}
volatile int c = 0;
int d = 0;
/**
* <pre>
* 第一个操作
* Normal Load
* Normal Store
*
* 第二个操作
* Volatile load
* MonitorEnter
*
* 结论:可以重排
* </pre>
*/
public void NormalLoad_NormalStore$$VolatileLoad_MonitorEnter() {
d = 0;
System.out.println(c);
}
int e = 0;
volatile int f = 0;
/**
* <pre>
* 第一个操作
* Normal Load
* Normal Store
*
* 第二个操作
* Volatile store
* MonitorExit
*
* 结论:不可以重排
* </pre>
*/
public void NormalLoad_NormalStore$$VolatileStore_MonitorExit() {
e = 1;
f = 1;
}
volatile int g = 0;
int h = 0;
/**
* <pre>
* 第一个操作
* Volatile load
* MonitorEnter
*
* 第二个操作
* Normal Load
* Normal Store
*
* 结论:不可以重排
* </pre>
*/
public void VolatileLoad_MonitorEnter$$NormalLoad_NormalStore() {
int a = g;
h = 1;
}
volatile int i = 0;
volatile int j = 0;
/**
* <pre>
* 第一个操作
* Volatile load
* MonitorEnter
*
* 第二个操作
* Normal Load
* Normal Store
*
* 结论:不可以重排
* </pre>
*/
public void VolatileLoad_MonitorEnter$$VolatileLoad_MonitorEnter() {
int a = i;
int b = j;
}
volatile int k = 0;
volatile int l = 0;
/**
* <pre>
* 第一个操作
* Volatile load
* MonitorEnter
*
* 第二个操作
* Volatile store
* MonitorExit
*
* 结论:不可以重排
* </pre>
*/
public void VolatileLoad_MonitorEnter$$VolatileStore_MonitorExit() {
int a = k;
int l = 1;
}
volatile int m = 0;
int n = 0;
/**
* <pre>
* 第一个操作
* Volatile store
* MonitorExit
*
* 第二个操作
* Normal Load
* Normal Store
*
* 结论:可以重排
* </pre>
*/
public void VolatileStore_MonitorExit$$NormalLoad_NormalStore() {
m = 1;
n = 1;
}
volatile int o = 0;
volatile int p = 0;
/**
* <pre>
* 第一个操作
* Volatile store
* MonitorExit
*
* 第二个操作
* Volatile load
* MonitorEnter
*
* 结论:不可以重排
* </pre>
*/
public void VolatileStore_MonitorExit$$VolatileLoad_MonitorEnter() {
o = 1;
int a = p;
}
volatile int r = 0;
volatile int s = 0;
/**
* <pre>
* 第一个操作
* Volatile store
* MonitorExit
*
* 第二个操作
* Volatile store
* MonitorExit
*
* 结论:不可以重排
* </pre>
*/
public void VolatileStore_MonitorExit$$VolatileStore_MonitorExit() {
r = 1;
s = 1;
}
我们知道java在运行的时候有两个地方可能用到重排序,一个是编译器编译的的时候,一个是处理器运行的时候。 那么我们就应该问问为啥要用指令重排序呢?
生活类比 我们从生活中举个例子,假设你有一箱红纸,现在要你剪成小红花贴在窗上。你有两种极端的选择:拿出来一个,把这个剪好,再贴上去......一个一个依次进行;另一种方式是先全部拿出来,然后全部剪好,最后全部贴上去。 那种效率更高?很明显是后者,因为前者你就需要不停地在箱子,剪刀和胶水之间切换,这个切换过程不仅浪费时间,还耗费精力。但是后者一直做一个工作也很无聊,还会导致半天了窗上一朵花都没有,会给你带来失落感,所以比较合适的做法就是拿出来一叠,把这一叠剪好,贴上去。这样既不无聊,也减少了切换次数,提高了工作效率。 再想想,如果有三个人,一个负责拿,一个负责剪,一个负责贴,就更快了。
作者:Mageek Chiu 链接:https://juejin.im/post/5b0b56f6f265da0dd6488083 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
图来自Java Memory Model - Reordering Problem
A field cannot be final and volatile at the same time, doing so is a compile time error.
definitions in concurrency theory:
Atomicity - is a property of operation that can be executed completely as a single transaction and can not be executed partially.
Visibility - if one thread made changes they are visible for other threads
Ordering - compiler is able to change an ordering of operations/instructions of source code to make some optimisations.
volatile[About] as an example
A
write
to avolatile
fieldhappens-before
every subsequentread
of that field.
Let's take a look at the example:
// Definitions
int a = 1;
int b = 2;
volatile boolean myVolatile = false;
// Thread A. Program order
{
a = 5;
b = 6;
myVolatile = true; // <-- write
}
//Thread B. Program order
{
Thread.sleep(1000); //just to show that writing into `myVolatile` was executed before
System.out.println(myVolatile); // <-- read
System.out.println(a); //prints 5, not 1
System.out.println(b); //prints 6, not 2
}
Visibility
- When Thread A changes/writes
a volatile
variable it also pushes all previous changes into RAM - Main Memory
as a result all not volatile
variable will be up to date and visible for another threads
Ordering:
All operations before writing into volatile variable in Thread A
will be called before. JVM is able to reorder them but guarantees that no one operation before writing into volatile variable in Thread A
will be called after it.
All operations after reading the volatile variable in Thread B
will be called after. JVM is able to reorder them but guarantees that no one operation after reading a volatile variable in Thread B
will be called before it.
指令重排
以上代码如果在一个线程内执行,不会出现问题,但是CPU会在他认为合理的情况下对两个不相关的指令进行重排,如果线程A执行writer,线程B执行reader,会有如下顺序:
线程A对1和2进行了重排
线程B对3和4进行了重排
因此,由于重排,导致不一样的结果,为了解决因重排出现的不一致问题,JMM规定了如下规则来避免发生不一致情况。
http://ifeve.com/jmm-cookbook-reorderings/ http://gee.cs.oswego.edu/dl/jmm/cookbook.html https://tech.meituan.com/2014/09/23/java-memory-reordering.html