并发-AtomicInteger源码分析—基于CAS的乐观锁实现

参考:

http://www.importnew.com/22078.html

https://www.cnblogs.com/mantu/p/5796450.html

http://hustpawpaw.blog.163.com/blog/static/184228324201210811243127/

https://www.cnblogs.com/binyue/archive/2013/11/22/3436999.html

https://blog.csdn.net/li_xunhuan/article/details/100654377

https://my.oschina.net/u/4418133/blog/4243606

 

 

 

 

1. 悲观锁与乐观锁

我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空寄存器,缓存数据。然后重新加载新的thread所需数据。当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过notify(),notifyAll()唤醒回来。在某个资源不可用的时候,就将cpu让出,把当前等待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤醒,让他进入runnable状态等待cpu调度。这就是典型的悲观锁的实现。独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

但是,由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事,所以悲观锁有很大的缺点。举个例子,如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。

所以就有了乐观锁的概念,他的核心思路就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。在上面的例子中,某个线程可以不让出cpu,而是一直while循环,如果失败就重试,直到成功为止。所以,当数据争用不严重时,乐观锁效果更好。比如CAS就是一种乐观锁思想的应用。

2.   java中CAS的实现

CAS就是Compare and Swap的意思,比较并操作。很多的cpu直接支持CAS指令。CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法,在运行CAS的平台上,运行时把它们编译为相应的机器指令。在Java.util.concurrent.atomic包下面的所有的原子变量类型中,比如AtomicInteger,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作。

在CAS操作中,会出现ABA问题。就是如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。有简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。AtomicStampedReference和AtomicMarkableReference支持在两个变量上执行原子的条件更新。AtomicStampedReference更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题,AtomicMarkableReference将更新一个“对象引用-布尔值”的二元组。

3.  AtomicInteger的实现。

AtomicInteger 是一个支持原子操作的 Integer 类,就是保证对AtomicInteger类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致问题。如果不使用 AtomicInteger,要实现一个按顺序获取的 ID,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样的 ID 的现象。

接下来通过源代码来看AtomicInteger具体是如何实现的原子操作。

首先看incrementAndGet() 方法,下面是具体的代码。

1
2
3
4
5
6
7
8
public final int incrementAndGet() { 
        for (;;) { 
            int current = get(); 
            int next = current + 1
            if (compareAndSet(current, next)) 
                return next; 
        
    }

通过源码,可以知道,这个方法的做法为先获取到当前的 value 属性值,然后将 value 加 1,赋值给一个局部的 next 变量,然而,这两步都是非线程安全的,但是内部有一个死循环,不断去做compareAndSet操作,直到成功为止,也就是修改的根本在compareAndSet方法里面,compareAndSet()方法的代码如下:

1
2
3
public final boolean compareAndSet(int expect, int update) { 
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 
    }

compareAndSet()方法调用的compareAndSwapInt()方法的声明如下,是一个native方法。

publicfinal native boolean compareAndSwapInt(Object var1, long var2, int var4, intvar5);

compareAndSet 传入的为执行方法时获取到的 value 属性值,next 为加 1 后的值, compareAndSet所做的为调用 Sun 的 UnSafe 的 compareAndSwapInt 方法来完成,此方法为 native 方法,compareAndSwapInt 基于的是CPU 的 CAS指令来实现的。所以基于 CAS 的操作可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。并且由于 CAS 操作是 CPU 原语,所以性能比较好。

类似的,还有decrementAndGet()方法。它和incrementAndGet()的区别是将 value 减 1,赋值给next 变量。

AtomicInteger中还有getAndIncrement() 和getAndDecrement() 方法,他们的实现原理和上面的两个方法完全相同,区别是返回值不同,前两个方法返回的是改变之后的值,即next。而这两个方法返回的是改变之前的值,即current。还有很多的其他方法,就不列举了。

下面以具体的例子分析下AtomicInteger的实现:

  计数器(Counter),采用Java里比较方便的锁机制synchronized关键字,初步的代码如下:

复制代码
 1 public class Counter {
 2     private int value;  
 3       
 4     public synchronized int getValue() {  
 5         return value;  
 6     }  
 7   
 8     public synchronized int increment() {  
 9         return ++value;  
10     }  
11   
12     public synchronized int decrement() {  
13         return --value;  
14     }  
15 }
复制代码

  synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁,这也就我们前面所说的悲观锁。这里会有些问题:首先,如果被阻塞的线程优先级很高很重要怎么办?其次,如果获得锁的线程一直不释放锁怎么办?(这种情况是非常糟糕的)。还有一种情况,如果有大量的线程来竞争资源,那CPU将会花费大量的时间和资源来处理这些竞争(事实上CPU的主要工作并非这些),同时,还有可能出现一些例如死锁之类的情况,最后,其实锁机制是一种比较粗糙,粒度比较大的机制,相对于像计数器这样的需求有点儿过于笨重,因此,对于这种需求我们期待一种更合适、更高效的线程安全机制。

package TestAtomicInteger;
 
import java.util.concurrent.atomic.AtomicInteger;
 
class MyThread implements Runnable {
//    static  int i = 0;
     static AtomicInteger ai=new AtomicInteger(0);
      
 
    public void run() {
        for (int m = 0; m < 1000000; m++) {
            ai.getAndIncrement();
        }
    }
};
 
public class TestAtomicInteger {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
 
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        t1.start();
        t2.start();
        Thread.sleep(500);
        System.out.println(MyThread.ai.get());
    }
}

  可以发现结果都是2000000,也就是说AtomicInteger是线程安全的。

  下面我们就以模拟CAS机制来实现Counter的例子:

   CAS类:

复制代码
 1 public class SimpleCAS {
 2     private volatile int value;
 3     public synchronized int getValue(){
 4         return value;  
 5     } 
 6     public synchronized boolean comperaAndSwap(int expectedValue,int newValue){
 7         int oldValue = value;
 8         if(oldValue == expectedValue){
 9             value = newValue;
10             return true;
11         }else{
12             return false;
13         }
14     }
15 }
复制代码

  CASCounter类:

复制代码
 1 public class CASCounter {
 2     private SimpleCAS cas;  
 3     public int getValue(){
 4         return cas.getValue();
 5     }
 6     public int increment(){
 7         int olevalue = cas.getValue();
 8         for (; ;) {
 9             if(cas.comperaAndSwap(olevalue, olevalue+1)){
10                 return cas.getValue();
11             }
12         }
13          
14     }
15 }
复制代码

  上面的模拟不是CSA的真正实现,其实我们在语言层面是没有做任何同步的操作的,大家也可以看到源码没有任何锁加在上面,可它为什么是线程安全的呢?这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!

  总结一下,AtomicInteger基于冲突检测的乐观并发策略。 可以想象,这种乐观在线程数目非常多的情况下,失败的概率会指数型增加。

 

 

 

 

 

AtomicInteger等对象出现的目的主要是为了解决在多线程环境下变量计数的问题,例如常用的i++,i--操作,它们不是线程安全的,AtomicInteger引入后,就不必在进行i++和i--操作时,进行加锁操作,在我们日常工作中,有很多业务场景需要在多线程环境下进行变量的计数:订单数统计、访问量统计、累计相应时长统计等。

demo 源码:https://github.com/mantuliu/javaAdvance

    下面我们先分析一下AtomicInteger的源代码。通过源码分析我们知道,AtomicInteger的核心就是一个CAS算法(CompareAndSwap),比较并交换算法,此算法是由unsafe的底层代码实现,它是一个原子的操作,原理就是:如果内存中的实际值与update值相同,则将实际值更新为expect值,反之则返回失败,由上层系统循环获取实际值后,再次调用此CAS算法:

复制代码
/*
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

/*
 *
 *
 *
 *
 *
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

package java.util.concurrent.atomic;
import sun.misc.Unsafe;

/**
 * An {@code int} value that may be updated atomically.  See the
 * {@link java.util.concurrent.atomic} package specification for
 * description of the properties of atomic variables. An
 * {@code AtomicInteger} is used in applications such as atomically
 * incremented counters, and cannot be used as a replacement for an
 * {@link java.lang.Integer}. However, this class does extend
 * {@code Number} to allow uniform access by tools and utilities that
 * deal with numerically-based classes.
 *
 * @since 1.5
 * @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;//value值的偏移地址

    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;//实际的值

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;//初始化
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final int get() {
        return value;//返回value值
    }

    /**
     * Sets to the given value.
     *
     * @param newValue the new value
     */
    public final void set(int newValue) {
        value = newValue;//设置新值,因为没有判断oldvalue,所以此操作是非线程安全的
    }

    /**
     * Eventually sets to the given value.
     *
     * @param newValue the new value
     * @since 1.6
     */
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);//与set操作效果一样,只是采用的是unsafe对象中通过偏移地址来设置值的方式
    }

    /**
     * Atomically sets to the given value and returns the old value.
     *
     * @param newValue the new value
     * @return the previous value
     */
    public final int getAndSet(int newValue) {//原子操作,设置新值,返回老值
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))//通过CAS算法,比较current的值和实际值是否一致,如果一致则设置为newValue
                return current;
        }
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * <p>May <a href="package-summary.html#Spurious">fail spuriously</a>
     * and does not provide ordering guarantees, so is only rarely an
     * appropriate alternative to {@code compareAndSet}.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful.
     */
    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {//i++操作
        for (;;) {
            int current = get();//获取当前值
            int next = current + 1;//当前值+1
            if (compareAndSet(current, next))//比较current值和实际的值是否一致,如不一致,则继续循环
                return current;
        }
    }

    /**
     * Atomically decrements by one the current value.
     *
     * @return the previous value
     */
    public final int getAndDecrement() {
        for (;;) {
            int current = get();
            int next = current - 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {//例如:当我们统计接口的响应时间时,可以利用此方法
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return current;
        }
    }

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

    /**
     * Atomically decrements by one the current value.
     *
     * @return the updated value
     */
    public final int decrementAndGet() {
        for (;;) {
            int current = get();
            int next = current - 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the updated value
     */
    public final int addAndGet(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return next;
        }
    }

    /**
     * Returns the String representation of the current value.
     * @return the String representation of the current value.
     */
    public String toString() {
        return Integer.toString(get());
    }


    public int intValue() {
        return get();
    }

    public long longValue() {
        return (long)get();
    }

    public float floatValue() {
        return (float)get();
    }

    public double doubleValue() {
        return (double)get();
    }

}
复制代码

      下面,我们为四种情况(同步关键字、ReentrantLock公平锁和非公平锁、AtomicInteger)做一下性能对比分析,当我们看到上面的代码分析后,我们判断AtomicInteger应该比加锁的方式快,但是实验的结果表明,AtomicInteger只比ReentrantLock加公平锁的情况快几十倍,比其它两种方式略慢一些。

     四个demo都用100个线程来循环模拟下单60秒钟:

demo Lesson8SyncIntPerform:在使用同步关键字加锁的情况下100个线程循环下单数为:677337556

demo Lesson8SyncIntPerform:在使用同步关键字加锁的情况下100个线程循环下单数为:755994691

demo Lesson8AtomicIntPerform:在使用AtomicInteger的情况下100个线程循环下单数为:562359607

demo Lesson8AtomicIntPerform:在使用AtomicInteger的情况下100个线程循环下单数为:575367967

demo Lesson8LockIntPerform:在使用ReentrantLock加非公平锁的情况下100个线程循环下单数为:857239882

demo Lesson8LockIntPerform:在使用ReentrantLock加非公平锁的情况下100个线程循环下单数为:860364303

demo Lesson8LockFairIntPerform:在使用ReentrantLock加公平锁的情况下100个线程循环下单数为:19153640

demo Lesson8LockFairIntPerform:在使用ReentrantLock加公平锁的情况下100个线程循环下单数为:19076567

   上面的实验结果表明,在jdk1.6及后续的版本中(本实验的jdk版本是1.7,操作系统为windows操作系统),已经对于synchronized关键字的性能优化了很多,已经和ReentrantLock的性能差不多,加锁的效果比不加锁时使用AtomicInteger性能效果还要略好一些,但是公平锁的性能明显降低,其它三种情况下的性能是公平锁性能的几十倍以上,这和公平锁每次试图占有锁时,都必须先要进等待队列,按照FIFO的顺序去获取锁,因此在我们的实验情景下,使用公平锁的线程进行了频繁切换,而频繁切换线程,性能必然会下降的厉害,这也告诫了我们在实际的开发过程中,在需要使用公平锁的情景下,务必要考虑线程的切换频率。

 

 

 

AtomicStampedReference解决ABA问题

在运用CAS做Lock-Free操作中有一个经典的ABA问题:

线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题,例如下面的例子:

AtomicStampedReference解决ABA问题 - 木瓜仙人 - 木瓜仙人

现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:

head.compareAndSet(A,B);

在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:

AtomicStampedReference解决ABA问题 - 木瓜仙人 - 木瓜仙人

此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:

AtomicStampedReference解决ABA问题 - 木瓜仙人 - 木瓜仙人

其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。

以上就是由于ABA问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference<E>也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题,例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新,AtomicInteger会成功执行CAS操作,而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败:

 

public class Test {

    private static AtomicInteger atomicInt = new AtomicInteger(100);

    private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0);

 

    public static void main(String[] args) throws InterruptedException {

       Thread intT1 = new Thread(new Runnable() {

           @Override

           public void run() {

              atomicInt.compareAndSet(100, 101);

              atomicInt.compareAndSet(101, 100);

           }

       });

 

       Thread intT2 = new Thread(new Runnable() {

           @Override

           public void run() {

              try {

                  TimeUnit.SECONDS.sleep(1);

              } catch (InterruptedException e) {

              }

              boolean c3 = atomicInt.compareAndSet(100, 101);

              System.out.println(c3); // true

           }

       });

 

       intT1.start();

       intT2.start();

       intT1.join();

       intT2.join();

 

       Thread refT1 = new Thread(new Runnable() {

           @Override

           public void run()

              try {

                  TimeUnit.SECONDS.sleep(1);

              } catch (InterruptedException e) {

              }

              atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);

              atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);

           }

       });

 

       Thread refT2 = new Thread(new Runnable() {

           @Override

           public void run() {

              int stamp = atomicStampedRef.getStamp();

              try {

                  TimeUnit.SECONDS.sleep(2);

              } catch (InterruptedException e) {

              }

              boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);

              System.out.println(c3); // false

           }

       });

 

       refT1.start();

       refT2.start();

    }

}

 

 

 

 

 

 

并发中的Native方法,CAS操作与ABA问题

1.JNI和Native方法

Java中,通过JNI(Java Native Interface,java本地接口)来实现本地化,访问操作系统底层,如系统硬件等

JNI的实现就是在Java里声明方法,然后编写C/C++实现该方法,步骤:

  • 编写带有native声明的方法的java类,得到.java文件
  • 使用javac命令编译所编写的java类,生成.class文件
  • 使用javah -jni java类名生成扩展名为h的头文件,也即生成.h文件
  • 使用C/C++(或者其他编程想语言)实现本地方法,创建.h文件的实现,也就是创建.cpp文件实现.h文件中的方法
  • 将C/C++编写的文件生成动态连接库,生成dll文件
  • 在Java中用System.loadLibrary()方法加载上面生成的动态链接库文件,这个native()方法就可以在Java中被访问了

不到万不得已不要使用JNI技术,一方面它需要你掌握更多的知识才可以驾驭,一方面使用了JNI你 的程序就会丧失可移植性。

2.sun.misc.Unsafe

Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。JDK中有一个类sun.misc.Unsafe,它提供了硬件级别的原子操作。

在Java.util.concurrent包里的很多代码实现都可以看到它的踪影。这个类尽管里面的方法都是public的,但是开发者是无法使用它的,只有在JDK内部实现中可以看到调用。

3.乐观锁和悲观锁

独占锁:是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。
乐观锁用到的机制就是CAS,Compare and Swap。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

4.CAS操作

CAS,Compare and Swap即比较并替换,设计并发算法时常用到的一种技术,
java.util.concurrent包很多地方都是使用的CSA,CAS是并发设计中非常重要。

CAS有三个操作数:

内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。

当前的处理器基本都支持CAS,只不过不同的厂家的实现不一样。

以AtomicInteger为例,研究在没有锁的情况下是如何做到数据正确性的

1
private volatile int value;

在没有锁的机制下需要借助volatile原语,在主存中直接操作,保证线程间的数据是共享的,
这样才获取变量的值的时候才能直接读取。

1
2
3
4
public final int get() {
//因为使用了volatile所以可以直接读取
return value;
}

然后来看看 ++i 是怎么做到的,代码来自JDK1.6:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
    * Atomically increments by one the current value.
    *
    * @return the previous value
    */
   public final int getAndIncrement() {
       for (;;) {
           int current = get();
           int next = current + 1;
           if (compareAndSet(current, next))
               return current;
       }
   }

 

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

而compareAndSet利用JNI来完成CPU指令的操作

1
2
3
4
5
6
7
/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     */
    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法

concurrent包下的原子类型都是这么实现的。

5.ABA问题

CAS:对于内存中的某一个值V,提供一个旧值A和一个新值B。如果提供的旧值V和A相等就把B写入V。这个过程是原子性的。
CAS执行结果要么成功要么失败,对于失败的情形下一班采用不断重试。或者放弃。
ABA:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。

如何解决ABA问题?

通常是使用版本戳来解决这个问题,避免并发中的问题。

点击查看 用AtomicStampedReference解决ABA问题

 

 

 

 

 

 

 

从JDK源码角度看什么时候使用CAS机制

一、引子

 如果我问你在Java语言环境下何时使用CAS机制,你可能会说:出现线程不安全可能性的时候就是我们应当使用CAS机制的时候。但是这个说话虽然是正确的,但是太笼统以至于说了好像没说一样。如果你学过synchronized关键字,你一定知道同步机制带来的内存上的损耗是很大的,比如频繁的上下文切换就是我们在使用synchronized关键字时急需避免的。但是如果你了解CAS机制的话,你就会知道此机制有可能会导致线程占据CPU资源,如果在线程安全的条件下仍然使用CAS机制,那么就会带来不必要的CPU资源损耗。

二、何时使用CAS机制

首先给出使用CAS机制的原则:

  1. 线程之间抢占资源不是特别激烈使用CAS机制,这保证了大部分线程不会是在干等资源的释放
  2. 等待资源释放时的CPU占用反而小于上下文切换所消耗的资源,使用CAS机制
  3. 线程可能出现不安全情况的条件下才使用CAS机制

解释:

  1. CAS机制由于往往和自锁(for(;;))机制相结合使用,所以在自旋机制下,线程竞争越激烈,越多的线程在循环中等待资源释放,而这个过程是占据CPU资源的
  2. 第二点的内涵是:我们需要确保synchronized关键字性能比CAS机制差
  3. 第三点的解释看似平常,但是却是我们平常不关注的地方,以下我们JDK源代码做解释:
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//得到访问锁对象的当前线程对象
            int c = getState();//得到当前锁对象的状态
            if (c == 0) {	//状态为0,意味着没有任何线程占据着当前锁对象
                if (compareAndSetState(0, acquires)) {//使用CAS机制将当前锁状态更新,只有一个线程会成功,返回true
                    setExclusiveOwnerThread(current);//将当前线程置为锁的独占线程
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//如果当前线程卡位占据锁对象的线程
                int nextc = c + acquires;//得到当前线程重入锁后的状态
                if (nextc < 0) // overflow//这是锁状态的非法值,如若此值,则抛出异常
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//调用set方法,更新状态值。
                return true;
            }
            return false;
        }

 以上代码是java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire中所定义的当前线程尝试获取资源的方法,可能你还没有学过AQS机制,Lock接口,但是通过我上述对代码的注释,相信你应该对这个代码块可以有一个大致的认识。
 不知道你有没有注意到一点,上述代码有两处用不同的方法进行锁状态的更新:
if (compareAndSetState(0, acquires)) 以及setState(nextc);
 但是为何目的都是锁对象状态更新,实现方式却是一个CAS机制,一个普通的set方法。
原因是上述原则中的第三点:CAS机制使用处可能出现线程不安全情况,而后者却是一定处于线程安全情况。下面来说说具体的判断原因:

  1. 首先说明上述代码块的锁特性:上述锁结构是一个独占锁,只允许一个线程占据锁资源,但是允许一个线程多次占据锁资源(重入);
  2. 当锁资源没有被任何线程占据,那么可能出现多个线程同时去抢占锁资源的情况,此时线程显然是不安全的,所以需要使用CAS机制来进行线程安全性的保证,并且多个抢占资源的线程中只有一个线程会抢占到所资源,所以将其放置于if逻辑判断语句中,只有成功的线程才会被设置为当前锁对象的独占线程;
  3. 而后者调用普通的set方法原因是:允许重入锁的条件是占据锁资源的线程恰好为当前访问锁对象的线程,这样的线程有且只有一个,那么进行状态更新时,就相当于我们尚未学习多线程知识前单线程的set方法,无须考虑线程不安全性,那么就无须使用CAS机制。

三、小结

 从CAS机制使用原则上我们还是可以看出一点,如果能笃定地根据代码逻辑判断出当前代码块是被单线程访问或者执行的,那么我们应当坚决拥护最简单的单线程中的写方法。不是说在学习好多线程知识之后我们在何时何处都应当使用多线程的写方法来保护线程安全性。如果多线程带来线程安全性保障是不必要的,那么多线程导致的额外损耗就是多余。

 

 

CAS和synchronized适用场景

1、对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized

 

1、使用CAS在线程冲突严重时,会大幅降低程序性能;CAS只适合于线程冲突较少的情况使用。

2、synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

 

posted on 2018-03-31 21:57  秦羽的思考  阅读(770)  评论(0编辑  收藏  举报