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的出現就是爲了解決一些簡單的併發操做,將比較、賦值做爲一個原子操做記性處理,實現無鎖化處理,節省資源開銷。