JUC之CAS

CAS(全稱爲CompareAndSwap,也有說是CompareAndSet,都差很少)是一條CPU併發原語,它的功能是判斷內存某個位置的值是否爲預期值,若是是則更改成新的值,判斷預期值和更改新值的整個過程是原子的。在JAVA中,CAS的實現所有在sun.misc.Unsafe類中的各個方法,調用UnSafe類中的CAS方法,JVM會幫咱們實現出CAS彙編指令,這是一種徹底依賴於硬件的功能。java

在傳統方式中實現併發的手段是加鎖,JAVA中的鎖有synchronized和Lock(jdk1.5纔有)。Lock是基於AQS和CAS實現的,這裏先跳過。對於synchronized鎖,JVM在執行它的時候會依賴操做系統的臨界區機制。這樣的話,每次執行到synchronized鎖,都會經歷用戶態和內核態之間的切換。這個過程的消耗是很大的。並且,大多數時候synchronized鎖住的操做是很細粒度的。爲了細粒度的操做去經歷用戶態和內核態之間的切換是低效的作法。併發

其實最多見的就是咱們須要併發修改某個變量值,舉個常見的例子,窗口售票,不加鎖的代碼以下所示:ide

public class Test {

    public static void main(String[] args) {
        Stock stock = new Stock(10);
        for (int i = 0; i < 5; i++) {
            new Thread(stock, "窗口" + (++i)).start();
        }
    }

    private static class Stock implements Runnable {
        private volatile int count;

        public Stock(int count) {
            this.count = count;
        }

        @Override
        public void run() {
            for (;;) {
                count--;
                if (count < 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "售出一張票,剩餘票數:" + count);
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

獲得的結果:
(票數和線程太少,須要多跑幾遍,多了幾乎一遍就能夠看出問題)性能

獲得的結果很明顯,一共賣出了12張票,其中紅框中出現的數字就能夠說明問題。出現問題的緣由很簡單,由於--count這個算式表達式並非原子的。在一個線程對count進行計算賦值後,但尚未將新值推送到內存中時,另外一個線程獲取的count值仍是原來的值,當這個線程拿着這個值去進行計算,就會出現上面的問題。(這個涉及到Java的內存模型JMM,有興趣的能夠自行了解)
在JDK1.5以前,咱們想要解決這個問題,就只能使用synchronized進行加鎖,以下:優化

public void run() {
for (;;) {
synchronized (this) {
count--;
}
if (count < 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "售出一張票,剩餘票數:" + count);
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

可是若是僅僅是相似於--count這種併發計數功能,須要進行同步的操做粒度很細時,使用synchronized就大材小用了,不高效(即使如今synchronized通過不少優化,再也不想最初那樣耗資源,可是它畢竟是個鎖,並且多個線程進行競爭的時候仍是會變成重量級鎖),而使用CAS來實現就會更加的輕量級,性能更好。先上代碼再說this

public class Test {

    private static Unsafe unsafe;

    public static void main(String[] args) throws Exception {
        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true);
        unsafe = (Unsafe) theUnsafeField.get(null);
        Stock stock = new Stock(10);
        for (int i = 0; i < 5; i++) {
            new Thread(stock, "窗口" + (++i)).start();
        }
    }

    private static class Stock implements Runnable {
        private volatile int count;
        private static long countOffset;
        public Stock(int count) {
            this.count = count;
            try {
                countOffset = unsafe.objectFieldOffset(this.getClass().getDeclaredField("count"));
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            for (;;) {
                int x = this.count;
                int y = x - 1;
                if (!unsafe.compareAndSwapInt(this, countOffset, x, y)) {
                    continue;
                }
                if (y < 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "售出一張票,剩餘票數:" + y);
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

像上面代碼同樣,當咱們使用CAS進行操做時,出現的效果和使用synchronized同樣的。代碼中使用了JAVA提供的sun.misc.Unsafe進行CAS操做,並且在代碼總我使用反射進行獲取Unsafe實例,之因此這樣作,是由於JDK不想讓咱們開發者去直接使用Unsafe這個類,並且使用起來比較繁瑣,他們給咱們提供了一些封裝好的類來供咱們開發者使用,好比經常使用的java.util.concurrent.atomic.AtomicInteger、java.util.concurrent.atomic.AtomicBoolean、java.util.concurrent.atomic.AtomicIntegerArray。這些類中都有相同的特色,就是使用sun.misc.Unsafe進行CAS操做,內部進行了一些相似上面代碼的封裝,咱們就以AtomicInteger進行代碼演示。atom

public class Test {

    public static void main(String[] args) throws Exception {
        Stock stock = new Stock(10);
        for (int i = 0; i < 5; i++) {
            new Thread(stock, "窗口" + (++i)).start();
        }
    }

    private static class Stock implements Runnable {
        private volatile AtomicInteger count;
        public Stock(int count) {
            this.count = new AtomicInteger(count);
        }

        @Override
        public void run() {
            for (;;) {
                int x = count.get();
                int y = x - 1;
                if (!count.compareAndSet(x, y)) {
                    continue;
                }
                if (y < 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "售出一張票,剩餘票數:" + y);
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

就像上面的代碼同樣,咱們沒必要進行反射獲取Unsafe,而後在獲取字段在class中的偏移量這些繁瑣的操做了,下面咱們就去看看AtomicInteger的源碼 spa

  

看上面AtomicInteger的源碼咱們能夠很明顯的看的出來,在AtomicInteger進行類加載的時候,會經過sun.misc.Unsafe獲取value這個變量在類文件中的偏移量,進行保存,跟咱們直接使用Unsafe的操做是同樣的。咱們找到剛纔使用的compareAndSet(x, y)方法的源碼,能夠看到底層就是使用unsafe實例進行CAS操做。操作系統

AtomicInteger還有一些別的方法,好比getAndIncrement、getAndDecrement、getAndAdd、incrementAndGet、decrementAndGet等等,底層實際上仍是使用的unsafe實例進行CAS操做,有興趣的同窗能夠本身翻下源碼看看,這裏就很少說了。線程

總結:CAS的出現就是爲了解決一些簡單的併發操做,將比較、賦值做爲一個原子操做記性處理,實現無鎖化處理,節省資源開銷。

相關文章
相關標籤/搜索