在JDK 5以前Java语言是靠synchronized关键字保证同步的,这会致使有锁(后面的章节还会谈到锁)。html
锁机制存在如下问题:java
(1)在多线程竞争下,加锁、释放锁会致使比较多的上下文切换和调度延时,引发性能问题。算法
(2)一个线程持有锁会致使其它全部须要此锁的线程挂起。数据结构
(3)若是一个优先级高的线程等待一个优先级低的线程释放锁会致使优先级倒置,引发性能风险。多线程
volatile是不错的机制,可是volatile不能保证原子性。所以对于同步最终仍是要回到锁机制上来。并发
独占锁是一种悲观锁,synchronized就是一种独占锁,会致使其它全部须要锁的线程挂起,等待持有锁的线程释放锁。而另外一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操做,若是由于冲突失败就重试,直到成功为止。
CAS 操做异步
上面的乐观锁用到的机制就是CAS,Compare and Swap。性能
CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。this
非阻塞算法 (nonblocking algorithms)atom
一个线程的失败或者挂起不该该影响其余线程的失败或挂起的算法。
现代的CPU提供了特殊的指令,能够自动更新共享数据,并且可以检测到其余线程的干扰,而 compareAndSet() 就用这些代替了锁定。
拿出AtomicInteger来研究在没有锁的状况下是如何作到数据正确性的。
private volatile int value;
首先毫无觉得,在没有锁的机制下可能须要借助volatile原语,保证线程间的数据是可见的(共享的)。这样才获取变量的值的时候才能直接读取。
public final int get() {
return value;
}
而后来看看++i是怎么作到的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在这里采用了CAS操做,每次从内存中读取数据而后将此数据和+1后的结果进行CAS操做,若是成功就返回结果,不然重试直到成功为止。
而compareAndSet利用JNI来完成CPU指令的操做。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
总体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操做都是利用相似的特性完成的。
而整个J.U.C都是创建在CAS之上的,所以对于synchronized阻塞算法,J.U.C在性能上有了很大的提高。
CAS看起来很爽,可是会致使“ABA问题”。
CAS算法实现一个重要前提须要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会致使数据的变化。
好比说一个线程one从内存位置V中取出A,这时候另外一个线程two也从内存中取出A,而且two进行了一些操做变成了B,而后two又将V位置的数据变成A,这时候线程one进行CAS操做发现内存中仍然是A,而后one操做成功。尽管线程one的CAS操做成功,可是不表明这个过程就是没有问题的。若是链表的头在变化了两次后恢复了原值,可是不表明链表就没有变化。所以前面提到的原子操做AtomicStampedReference/AtomicMarkableReference就颇有用了。这容许一对变化的元素进行原子操做。
===================================================
总结:CAS是硬件CPU提供的元语,它的原理:我认为位置 V 应该包含值 A;若是包含该值,则将 B 放到这个位置;不然,不要更改该位置,只告诉我这个位置如今的值便可。
Java并发库中的AtomicXXX类均是基于这个元语的实现,以AtomicInteger为例:
publicfinalint incrementAndGet() {
for (;;) {
int current = get();
int next = current +1;
if (compareAndSet(current, next))
return next;
}
}
publicfinalboolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
其中,unsafe.compareAndSwapInt()是一个native方法,正是调用CAS元语完成该操做。
CAS缺点
CAS虽然很高效的解决原子操做,可是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操做
1. ABA问题。由于CAS须要在操做值的时候检查下值有没有发生变化,若是没有发生变化则更新,可是若是一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,可是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法做用是首先检查当前引用是否等于预期引用,而且当前标志是否等于预期标志,若是所有相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
关于ABA问题参考文档: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
2. 循环时间长开销大。自旋CAS若是长时间不成功,会给CPU带来很是大的执行开销。若是JVM能支持处理器提供的pause指令那么效率会有必定的提高,pause指令有两个做用,第一它能够延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它能够避免在退出循环的时候因内存顺序冲突(memory order violation)而引发CPU流水线被清空(CPU pipeline flush),从而提升CPU的执行效率。
3. 只能保证一个共享变量的原子操做。当对一个共享变量执行操做时,咱们可使用循环CAS的方式来保证原子操做,可是对多个共享变量操做时,循环CAS就没法保证操做的原子性,这个时候就能够用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操做。好比有两个共享变量i=2,j=a,合并一下ij=2a,而后用CAS来操做ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你能够把多个变量放在一个对象里来进行CAS操做。
因为java的CAS同时具备 volatile 读和volatile写的内存语义,所以Java线程之间的通讯如今有了下面四种方式:
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操做,这是在多处理器中实现同步的关键(从本质上来讲,可以支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,所以任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操做的原子指令)。同时,volatile变量的读/写和CAS能够实现线程之间的通讯。把这些特性整合在一块儿,就造成了整个concurrent包得以实现的基石。若是咱们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从总体来看,concurrent包的实现示意图以下: