樂觀鎖的一種實現方式——CAS

   在java裏面,synchronized關鍵字就是一種悲觀鎖,由於在加上鎖以後,只有當前線程能夠操做變量,其餘線程只有等待。java

  CAS操做是一種樂觀鎖,它假設數據不會產生衝突,而是在提交的時候再進行版本比較。這樣能夠減小加鎖的頻率,提升程序的性能。算法

線程安全

衆所周知,Java是多線程的。可是,Java對多線程的支持實際上是一把雙刃劍。一旦涉及到多個線程操做共享資源的狀況時,處理很差就可能產生線程安全問題。線程安全性多是很是複雜的,在沒有充足的同步的狀況下,多個線程中的操做執行順序是不可預測的。安全

   Java裏面進行多線程通訊的主要方式就是共享內存的方式,共享內存主要的關注點有兩個:可見性和有序性。加上覆合操做的原子性,咱們能夠認爲Java的線程安全性問題主要關注點有3個:可見性、有序性和原子性。多線程

   Java內存模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的問題。這裏再也不詳細介紹JMM及鎖的其餘相關知識。可是咱們要討論一個問題,那就是鎖究竟是不是有利無弊的?  性能

   鎖存在的問題this

   Java在JDK1.5以前都是靠synchronized 關鍵字保證同步的,這種經過使用一致的鎖定協議來協調對共享狀態的訪問,能夠確保不管哪一個線程持有共享變量的鎖,都採用獨佔的方式來訪問這些變量。獨佔鎖其實就是一種悲觀鎖,因此能夠說    synchronized 是悲觀鎖。  spa

   悲觀鎖機制存在如下問題:線程

   在多線程競爭下,加鎖、釋放鎖會致使比較多的上下文切換和調度延時,引發性能問題。code

     一個線程持有鎖會致使其它全部須要此鎖的線程掛起。若是一個優先級高的線程等待一個優先級低的線程釋放鎖會致使優先級倒置,引發性能風險。而另外一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。與鎖相比,volatile 變量是一個更輕量級的同步機制,由於在使用這些變量時不會發生上下文切換和線程調度等操做,可是 volatile 不能解決原子性問題,所以當一個變量依賴舊值時就不能使用volatile 變量。所以對於同步最終仍是要回到鎖機制上來。  內存

   

樂觀鎖

   樂觀鎖(Optimistic Locking )實際上是一種思想。相對悲觀鎖而言,樂觀鎖假設認爲數據通常狀況下不會形成衝突,因此在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,若是發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去作。  

   上面提到的樂觀鎖的概念中其實已經闡述了他的具體實現細節:主要就是兩個步驟:衝突檢測和數據更新。其實現方式有一種比較典型的就是Compare and Swap (CAS )。  

 

CAS

   CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知此次競爭中失敗,並能夠再次嘗試。

   CAS 操做包含三個操做數 —— 內存位置(V)、預期原值(A)和新值(B)。若是內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值。不然,處理器不作任何操做。不管哪一種狀況,它都會在 CAS 指令以前返回該位置的值。(在 CAS 的一些特殊狀況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了「我認爲位置 V 應該包含值 A;若是包含該值,則將 B 放到這個位置;不然,不要更改該位置,只告訴我這個位置如今的值便可。 」這其實和樂觀鎖的衝突檢查+數據更新的原理是同樣的。  

    這裏再強調一下,樂觀鎖是一種思想。CAS是這種思想的一種實現方式。

Java對CAS的支持

 

    在JDK1.5 中新增java.util.concurrent就是創建在CAS之上的。相對於對於synchronized 這種阻塞算法,CAS是非阻塞算法的一種常見實現。因此JUC在性能上有了很大的提高。  

   咱們以java.util.concurrent 中的AtomicInteger 爲例,看一下在不使用鎖的狀況下是如何保證線程安全的。主要理解getAndIncrement 方法,該方法的做用至關於++i 操做。  

 

public class AtomicInteger extends Number implements java.io.Serializable {          private volatile int value;      public final int get() {          return value;      }   public final int getAndIncrement() {    for (;;) {        int current = get();        int next = current + 1;        if (compareAndSet(current, next)){  //該操做先檢查當前數值是否等於current,等於意味着AtomicInteger的值沒有被其餘線程修改過,則將AtomicInteger的當前數值更新成next的值,若是不等compareAndSet方法會返回false,程序會進入for循環從新進行compareAndSet操做            return current;    //返回的是當前的值
        }
   } } public final int incrementAndGet() {    for (;;) {        int current = get();        int next = current + 1;        if (compareAndSet(current, next))            return next;          //返回的是加一以後的值    } }    public final boolean compareAndSet(int expect, int update) {          return unsafe.compareAndSwapInt(this, valueOffset, expect, update);      }   }

 

在沒有鎖的機制下須要字段value藉助volatile原語,保證線程間的數據是可見的。這樣在獲取變量的值的時候才能直接讀取。而後來看看    ++i 是怎麼作到的。  

 

   getAndIncrement 採用了CAS操做,每次從內存中讀取數據而後將此數據和    +1 後的結果進行CAS操做,若是成功就返回結果,不然重試直到成功爲止。而    compareAndSet 利用JNI來完成CPU指令的操做。  

 

 

總結

 

Java中的線程安全問題相當重要,要想保證線程安全,就須要鎖機制。鎖機制包含兩種:樂觀鎖與悲觀鎖。悲觀鎖是獨佔鎖,阻塞鎖。樂觀鎖是非獨佔鎖,非阻塞鎖。有一種樂觀鎖的實現方式就是CAS ,這種算法在JDK 1.5中引入的    java.util.concurrent 中有普遍應用。可是值得注意的是這種算法會存在ABA問題。  

相關文章
相關標籤/搜索