Java并发问题主要有三个核心概念:原子性,可见性,顺序性。
原子性
并发问题的原子性的概念和数据库事务的原子性是一样的:一个操作的多个步骤要么一起生效,要么一起回滚。
java自带的并发控制的手段中,可以保障原子性的有:
- synchronized 关键字
- CAS
可见性
可见性的问题是指当变量被多个线程使用时,一个线程对其作出的修改操作,是否后续其他线程读的时候能马上读到修改后的值。可见性问题的产生是因为CPU为了加速数据读取读取的速度,采用了多级的告诉缓存。
java中保障可见性的手段有
- synchronized关键字
- volidate关键字
顺序性
顺序指的是代码的执行顺序,顺序性指的是java编译器和CPU,在保障单个线程执行的时代码的逻辑结果不变的前提下,可能会对代码执行顺序的作出的一些改变(优化)。代码块在单线程执行的情况下,不管顺序怎么变,jvm一定会保证其逻辑不变,但是多个线程的情况下如果发生了重排序,则局部的逻辑可能是没变,单是全局的逻辑已经有问题了,比如下面的代码块
class BadlyOrdered {
boolean a = false;
boolean b = false;
void threadOne() {
a = true;
b = true;
}
boolean threadTwo() {
boolean r1 = b; // sees true
boolean r2 = a; // sees false
return r1 && !r2; // returns true
}
}
方法threadOne在单线程执行的情况下,不管顺序怎么变,其最终的结果都是a和b都赋值为true。但是如果是多个线程,线程1执行方法threadOne,线程2执行方法threadTwo,即使不考虑可见性的问题,因为threadOne的执行顺序被排序,所以b的值得先被赋值为true,然后线程2读到了b=true和a=false,最后返回的结果将是true。
那么怎么解决多线程情况下,顺序性带来的不确定问题呢。一种方法是在编码时显示使用synchronized关键,做到同一时间只有1个线程能操作共享的数据。另外一个方法运用jvm自带的happens-before规则。
happens-before
happens-before规则的描述如下
Happens-Before Relationship : Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.
可见,happens-before规则同时对可见性和顺序产生的了影响。happens-before这个名称感觉还是挺有歧义的,从"before"这个字眼上看,以为它定义的是顺序性,其实"before"描述的是可见性的"before"。而顺序性,并不能简单的从字面描述去理解。比如,java规范定义了多条满足的happens-before规则,有一条是:
A write to a volatile field happens-before every subsequent read of that volatile.
它的"before"的定义,不能简单理解为对一个volatile变量的写操作发生在对其读操作之前。一个线程写,一个线程读,jvm可不知道哪个会先执行到。这里的"before"是可见性的before,也就是,一个volatile变量的写操作以及其后面的操作,对另外一个线程的发生在这之后的volatile变量的读操作以及之后的操作可见。
而这条规则,对顺序性的真正的影响是:
- 禁止把volatile写之前的行为与它重排序
- 禁止把volatile读之后的行为与它重排序
对于上面的示例程序,只要把变脸b用volatile关键字修饰,那threadOne方法就不会发生重排序了。也就是说happens-before规则也规定哪些类型的操作之间,编译器和CPU不能对其进行重排序,编程的时候只要利用这些内置的规则,就能消除重排序在多线程环境下的问题。
总结来说,顺序性是java为了单线程执行的时候做的优化,但是它会在多线程环境下带来问题,并且这些问题基本是不能在编码阶段做理论分析的。为了解决这个问题,java又自己内置了一些规则(happens-before规则)对重排序做了一些限制,多线程编程的时候,只要利用这些规则,确保我们关心的关键点不会发生重排序,也就保证了程序的正确性。
Lock接口
我们知道Lock是jdk提供的并发控制工具包,使用Lock也能保证java并发的三个重要特性的。但是需要说的Lock只是一个工具包,它提供的保障都是依赖jvm提供的更底层的机制来保障的
- 可见性: 是对实现中的锁变量使用volidate关键字,然后依赖volidate带来的happen-before特性,保障加锁和解锁之间的修改对其他线程可见的
- 原子性:Lock本身的加锁解锁这两个行为的原子性是通过CAS操作保障的,而加锁之后的行为的原子性,是通过控制同一时间只能由1个线程执行,逻辑保障的
- 顺序性: 同原子性,也是通过控制同一时间只能由1个线程执行的保障的
总结
原子性 | 可见性 | 顺序性 | |
---|---|---|---|
sychronized | 保证 | 保证 | 保证 |
volatile | 不保证 | 保证 | 部分保证 |
CAS操作 | 保证 | 不保证 | 不保证 |
Lock工具类 | 保证 | 保证 | 保证 |
####### refer
http://www.infoq.com/cn/articles/java-memory-model-2
http://ifeve.com/easy-happens-before/
http://blog.csdn.net/admiral_dota/article/details/50489882