xylz,imxylz

          關注后端架構、中間件、分布式和并發編程

             :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks

          在JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這會導致有鎖(后面的章節還會談到鎖)。

          鎖機制存在以下問題:

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

          (2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。

          (3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險。

          volatile是不錯的機制,但是volatile不能保證原子性。因此對于同步最終還是要回到鎖機制上來。

          獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。

          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操作成功,但是不代表這個過程就是沒有問題的。如果鏈表的頭在變化了兩次后恢復了原值,但是不代表鏈表就沒有變化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對變化的元素進行原子操作。

           

           

          參考資料:

          (1)非阻塞算法簡介

          (2)流行的原子

           



          ©2009-2014 IMXYLZ |求賢若渴
          posted on 2010-07-04 18:03 imxylz 閱讀(48027) 評論(19)  編輯  收藏 所屬分類: J2EE

          評論

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2010-07-05 15:24 nick
          繼續繼續  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2010-11-13 18:41 HyviTan
          Hi

          文章結尾的兩個參考文獻鏈接失效了。  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2011-03-07 20:51 caihj
          “上面的樂觀鎖用到的機制就是CAS,Compare and Swap”
          是Swap?不是Set?
            回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2011-05-04 13:00 kick
          請教博主,從網上找了一段模擬CAS的代碼,能夠實現i++功能,改了一下。開了10000個線程運行,結果是錯誤的,達不到10000. 為什么?
          public class CasCounter {
          private SimulatedCAS simulatedCAS = null;
          public int getValue(){
          return simulatedCAS.getValue();
          }

          public int increment(){
          int oldValue = simulatedCAS.getValue();
          while(simulatedCAS.compareAndSwap(oldValue, oldValue+1) != oldValue)
          oldValue = simulatedCAS.getValue();
          return oldValue;
          }

          public CasCounter(SimulatedCAS cas){
          this.simulatedCAS = cas;
          }

          public static void main(String[] args) throws InterruptedException {
          CasCounter counter = new CasCounter(new SimulatedCAS());
          Thread[] ts = new Thread[10000];
          for(int i = 0; i <10000; i++){
          ts[i] = new CounterThread(counter);

          }
          for(Thread t : ts){
          t.start();
          }

          for(Thread t : ts){
          t.join();
          }

          System.out.println("counter: "+ counter.getValue());
          }
          }

          public class CounterThread extends Thread {
          CasCounter counter = null;

          public CounterThread(CasCounter counter) {

          this.counter = counter;
          }

          @Override
          public void run() {
          counter.increment();
          }
          }

          public class SimulatedCAS {

          private volatile int value = 0;
          public synchronized int getValue(){
          return value;

          }
          public synchronized int compareAndSwap(int expectedValue, int newValue){
          if(value == expectedValue)
          value = newValue;
          return expectedValue;
          }
          }
            回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2011-08-09 20:07 xxx
          @kick
          問題可能出在CasCounter.increment()函數中。
          多個線程可能取到相同的 oldValue,會導致線程修改失效。
          如果給這個函數加上 synchronized差不多就可以了。

          不過郁悶的是,加上synchronized以后,也會出現達不到10000的情況。
          不過比不加synchronized的情況好的多。不知道是為什么了  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2011-08-09 21:29 xylz
          @xxx
          @kick

          出錯的地方在于CAS操作,稍微改動一下:
          public synchronized int compareAndSwap(int expectedValue, int newValue) {
          if (value == expectedValue) {
          value = newValue;
          return expectedValue;
          }
          return - expectedValue;
          }
          就可以得到正確結果的。通常CAS操作可以返回true/false,如果操作成功返回true,否則返回false。原來的代碼是不管操作是否成功都返回了期待值,所以就改變了操作邏輯。
            回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2011-08-11 15:49 zhangxl
          @xylz
          這里的模擬都使用了synchronized(內在鎖)關鍵字,模擬CAS還有什么意義呢?引入CAS的目的不就是為了較少鎖的競爭,提高多線程并發的吞吐率嗎?
          我覺得要模擬也應該像AQS那樣,比如,這是AQS的源碼中狀態變量的原子操作:

          A.Q.S里面包含了一個存儲同步狀態的變量,它的聲明如下:

          private volatile int state;

          這里采用了volatile修飾符的原因是為了保證對state變量的寫對所有的線程都是可見的。但是大家都知道,volatile只能保證變量的可見性,不能保證對變量操作的原子性,所以A.Q.S里面就采用了CAS(Compare And Swap)操作來更新state變量的值,代碼如下:

          protected final boolean compareAndSetState(int expect, int update)
          { // See below for intrinsics setup to support this
          return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }


          個人覺得這樣才能模擬出CAS的本質,原子特性。
            回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2011-08-13 20:18 yintiefu
          @kick
          修改2個地方的代碼

          1.
          public int increment() {

          for(;;){
          int oldValue = simulatedCAS.getValue();
          if(simulatedCAS.compareAndSwap(oldValue,oldValue+1)){
          oldValue = simulatedCAS.getValue();
          return oldValue;
          }
          }
          }

          2.
          public synchronized boolean compareAndSwap(int expectedValue, int newValue) {
          if (value == expectedValue){
          value = newValue;
          return true;
          }
          return false;
          }  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2011-08-26 10:53 imtxt
          @zhangxl
          您講的是上面的回復貼出的代碼?本來那就是拿來做好玩用的吧。  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2012-07-09 11:39 waitingfortime
          @xylz
          這個代碼不做修改,也可以得到正確的數啊?怎么得不到呢  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2013-03-03 14:40 teasp
          “獨占鎖是一種悲觀鎖”,這是錯誤的說法。  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2013-03-06 21:44 asdf
          @teasp
          別話說一半,解釋一下  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2013-03-07 12:54 teasp
          @asdf
          獨占鎖既可以是悲觀的也可以是樂觀的呀,用CAS實現的獨占鎖不就是樂觀的?  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2013-06-16 13:24 flying
          用博主的 int compareAndSwap方法返回- expectedValue,有時會出現9999,用boolean是對的,應該是expectedValue為零時,-0和0是相同的值,導致沒有加成功。  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2013-06-16 13:31 flying
          @xylz
          SimulatedCAS 類的value 是volatile ,getValue的synchronized 關鍵字是不是可以去掉了?
            回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2014-08-06 21:04 vanjayzhou
          借這里問個問題哈,java 當中的cas問題,java concurrent 包的實現是依賴 CAS準則實現的,對AtomicInteger 方法中的 compareAndSet方法有些疑惑。
          public final boolean compareAndSet(int expect, int update) {
          return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
          }

          unsafe.compareAndSwapInt方法是本地方法, 我考慮這么一種情況:
          兩個線程T1, T2 同時取了主內存中A, T1調用compareAndSet 方法成功,將A變成 A1, 因為A值是同時從內存中取出來的,那T2調用compareAndSet方法也會成功,將A變成A2. 那結果不是不正確了么。
          幫我分析一下啊。
          謝啦
            回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4 2014-08-08 10:13 imxylz
          @vanjayzhou
          CAS的CPU原語保證A修改時,T2修改會失敗。所以T2才需要循環操作,直到成功。  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2014-10-31 16:58 JJZHK
          @vanjayzhou
          老版的CPU會進行鎖總線,新版的CPU進行的是緩存鎖定。所以T2修改的時候肯定會失敗的,因為共享內存已經被CPU鎖定了。  回復  更多評論
            

          # re: 深入淺出 Java Concurrency (5): 原子操作 part 4[未登錄] 2016-01-06 08:40 William Chen
          沒錯是交換設置。  回復  更多評論
            


          ©2009-2014 IMXYLZ
          主站蜘蛛池模板: 北宁市| 湘西| 碌曲县| 铜川市| 安阳市| 邢台县| 梁河县| 长沙县| 井研县| 湖南省| 黄平县| 和田县| 旬阳县| 石屏县| 土默特左旗| 绍兴县| 新田县| 北京市| 宁城县| 娄烦县| 华亭县| 昂仁县| 双流县| 枞阳县| 湾仔区| 会同县| 恭城| 章丘市| 新平| 双鸭山市| 奇台县| 远安县| 余江县| 屏山县| 克拉玛依市| 临颍县| 蓬安县| 巴塘县| 新兴县| 桑植县| 仁化县|