本文直接让你读懂多线程情况下的三大特性和happens-before关系,get核心技能,如果基础不够可以查阅作者并发编程栏目文章。
原子性 其本质意思是不可分割, 是指对一系列操作时,这些操作只有全部执行(success),或者全部不执行(fail),不存在第三种情况;当初学习数据库相关知识的时候大家应该都知道若数据库支持事物操作,那么其有四大特性,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)持久性(Durability);同理多线程的的原子性其思路跟数据库的操作的原子性是一致的。举个例子(银行转账),如果你向A先生转账100元,在这个过程中,第一步先要从你的账户中划出100元,第二步将100元划到A先生的账户;在这个例子中所谓的原子操作是指要么转账成功100元划至A先生账户(commit),要么转账失败钱退回你的账户(rollback);在这个例子中的非原子性操作是指你钱转出去了,中途程序出问题,A先生的账号没有收到100元;多线程中的原子性定义就是:对一系列操作,这些操作一旦开始进行就必须直到结束,中间不能切换至其他线程,要么操作成功,要么操作失败;
示例代码:
public class Atomicity {
private int i;
public int getCount(){
return i++;
}
}
反汇编结果:
public class com.youku1327.base.thread.Atomicity {
public com.youku1327.base.thread.Atomicity();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int getCount();
Code:
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: dup_x1
6: iconst_1
7: iadd
8: putfield #2 // Field i:I
11: ireturn
}
我们重点看第2步至第8步;第2步是变量 i 加载进栈;第5步是 复制了一份 变量 i 重新入栈;第6步是加载常量1进栈;第7步对变量 i 和 常量 1进行相加 操作;第8步是将加1后的变量 i 重新赋值给原来的变量 i 并且出栈;很明显 i++ 这个操作在这个过程中进行了3个步骤(加载变量,变量和常量相加,变量出栈重新赋值); 在java的JMM模型中,我们可以这样认为,先将变量 i 提取到本地内存,然后进行 副本变量 i 和 常量 1 相加操作,最后将本地内存刷至主存;那么在多线程的情况下,就有可能 2 个线程都拿到了变量 i 加载至 本地内存,都对 变量副本 i 和常量 1进行相加,最后同时刷至主存,那么本来应该是i的值是3,最终的结果 i的值是2;可以预见同时有2个线程可以对变量i进行操作,那么我们就认为这个操作是非原子性;
官方文档描述如下:
Reads and writes are atomic for reference variables and for most primitive variables (all types except long and double).
Reads and writes are atomic for all variables declared volatile (including long and double variables).
可见性指在多线程情况下,如果一个线程对变量进行了修改,其他线程是可见的,能读取到变量最新的改变结果就是可见性;以JMM模型来分析就是多个线程同时将主存的变量x提取到本地内存,当有线程A对这个变量x进行修改,其他线程是可见的,那么其他线程只能等待线程A将本地内存刷至主存,然后重新获取变量x; 如果其他线程对变量x的修改不可见,那么就有可能多个线程都对变量x做了修改,然后都刷至主存,引发多线程缓存不一致问题;
重排序是指JMM模型中允许编译器对代码进行重排序进行优化;在单线程情况下不会发生问题,在多线程情况下会影响程序执行的正确性;多线程情况下如果能保证重排序不会影响程序的正确性就是程序的有序性;
private void getOrder(){
int x = 0;
int y = 1;
int z = x + y;
}
我们以最简单的示例来理解重排序,经过编译器优化后进行重排序那么有可能 x 的赋值,落后 于 y 的赋值,如果程序是有序性的 z 因为 依赖于 x , y , 所有 z 的赋值永远都在 x , y 赋值之后执行;如果程序不是有序性那么z 的赋值有可能发生在 x, y 的赋值之前,这就会导致程序出现异常;as-if-serial 语义中 就是单线程中无论 程序怎么重排序,都不会影响程序的最终结果,编译器永远不会对存在依赖关系的变量进行重排序,也跟这个是同样的道理;
happens-before 是指两个操作之间的执行顺序,可以是一个线程,也可以是多个线程进行操作;JMM模型中 happens-before 关系是可以保证程序的可见性;其具体意思指:A操作 happens-before B, 那么A 操作发生于 B操作之前,A 操作的结果是对B可见,JMM允许编译重排序,只要程序的结果正确,那么编译器可以不按照 happens-before 关系严格执行;