Java并发——原子变量和原子操做

      不少状况下咱们只是须要一个简单的、高效的、线程安全的递增递减方案。注意,这里有三个条件:简单,意味着程序员尽量少的操做底层或者实现起来要比较容易;高效意味着耗用资源要少,程序处理速度要快;线程安全也很是重要,这个在多线程下能保证数据的正确性。这三个条件看起来比较简单,可是实现起来却难以使人满意。html

      一般状况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操做:得到变量当前值,为该值+1/-1,而后写回新的值。在没有额外资源能够利用的状况下,只能使用加锁才能保证读-改-写这三个操做是“原子性”的。java

      Java 5新增了AtomicInteger类,该类包含方法getAndIncrement()以及getAndDecrement(),这两个方法实现了原子加以及原子减操做,可是比较不一样的是这两个操做没有使用任何加锁机制,属于无锁操做。      程序员

      在JDK 5以前Java语言是靠synchronized关键字保证同步的,这会致使有锁(后面的章节还会谈到锁)。算法

      锁机制存在如下问题:安全

      (1)在多线程竞争下,加锁、释放锁会致使比较多的上下文切换和调度延时,引发性能问题。数据结构

      (2)一个线程持有锁会致使其它全部须要此锁的线程挂起。多线程

      (3)若是一个优先级高的线程等待一个优先级低的线程释放锁会致使优先级倒置,引发性能风险。性能

      volatile是不错的机制,可是volatile不能保证原子性。所以对于同步最终仍是要回到锁机制上来。this

      独占锁是一种悲观锁,synchronized就是一种独占锁,会致使其它全部须要锁的线程挂起,等待持有锁的线程释放锁。而另外一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操做,若是由于冲突失败就重试,直到成功为止。spa

CAS 操做

      上面的乐观锁用到的机制就是CAS,Compare and Swap。

      CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。

非阻塞算法 (nonblocking algorithms)

一个线程的失败或者挂起不该该影响其余线程的失败或挂起的算法。

      现代的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构建非阻塞计数器、队列等数据结构。

      CAS看起来很爽,可是会致使“ABA问题”。

      CAS算法实现一个重要前提须要取出内存中某时刻的数据,而在下时刻比较并替换,可是在这个时间差内任何变化均可能发生。

      好比说一个线程one从内存位置V中取出A,这时候另外一个线程two也从内存中取出A,而且two进行了一些操做变成了B,而后two又将V位置的数据变成A,这时候线程one进行CAS操做发现内存中仍然是A,而后one操做成功。尽管线程one的CAS操做成功,可是不表明这个过程就是没有问题的。若是链表的头在变化了两次后恢复了原值,可是不表明链表就没有变化。要解决"ABA问题",咱们须要增长一个版本号,在更新变量值的时候不该该只更新一个变量值,而应该更新两个值,分别是变量值和版本号,AtomicStampedReference支持在两个变量上进行原子的条件更新,可使用该类进行更新操做。

参考资料:

(1)非阻塞算法简介

(2)流行的原子

 

转自:http://www.blogjava.net/xylz/archive/2010/07/04/325206.html

相关文章
相关标签/搜索