1. 程式人生 > >並發之無鎖技術歸納

並發之無鎖技術歸納

時間 現象 math 應用 成功 and initials package ray

並發之AtomicBoolean/AtomicBooleanArray/AtomicBooleanUpdateFeild 1 和前面的AtomicInteger很相似或者原理基本一致的;原理就是使用了CAS算法實行循環重試的方式來保證一組操作是原子性的操作; 2 同樣的也是一個無鎖技術的應用; 3 在源碼內部,使用1表示true,使用0表示false; 同樣的boolean類型的變量在並發情況下也是不安全的,因此使用了AtomicBoolean來保障原子性的操作; AtomicReference案例: 和AtomicInteger、AtomicLong原大同小異;只是AtomicLong而言,32位的數分為高16位和低16位分別並行計算;
AtomicReference是一種模板類,可以用來封裝任何類型的數據;
public class AtomicReference<V> implements java.io.Serializable

package automic;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicRefrenceTest {
     public final static AtomicReference<String> atomicString = new AtomicReference<String>("gosaint");
     
/** * 創建了10個線程,同時去修改atomicString的值,但是在並發狀態下只有一個可以修改成功! * @param args */ public static void main(String[] args) { // 開啟10個線程 for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() {
try { Thread.sleep(Math.abs((int) Math.random() * 100)); } catch (Exception e) { e.printStackTrace(); } if (atomicString.compareAndSet("gosaint", "mrc")) { System.out.println(Thread.currentThread().getId() + "Change value"); } else { System.out.println(Thread.currentThread().getId() + "Failed"); } } }).start(); } } }

看運行結果:只有一個線程對值進行了修改;
10Change value
9Failed
11Failed
13Failed
14Failed
15Failed
12Failed
16Failed
18Failed
17Failed

CAS的缺點: 其實這個之前說過,為了深刻理解,這裏再說明一下;比如在內存的初始值是3;現在存在兩個線程去修改這個值;根據JVM內存模型;每一個線程都存在這個變量的副本;假設線程1去修改這個值;發現內存中的這個值是3;想要修改為4;但是此時線程2也去修改了內存的值;並且鮮牛該為2,再修改為3,此時線程1發現還是內存還是3,進行了修改;我們看到,雖然對於線程1來說,修改完成了,但是內存中的3確是經過修改2修改過的,這個值得狀態已經發生了變化;如果我們對於某個值的狀態很關註並且這個狀態很重要;那麽這個漏洞必須要避免;這也就是傳說中的“ABA”問題;在JAVA中,通常是對內存中值得每一次修改添加一個時間戳作為版本號,再比較值的時候同樣的時間戳的版本號也要比較; 在AtomicStampedReference類中,存在一個內部類Pair來封裝值和時間戳;
private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
 
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

上面的源碼在比較值得時候時間戳的信息同樣的也進行比較;
package automic;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicTest {
     /**
      * AtomicStampedReference(V initialRef, int initialStamp)
      *   創建具有給定初始值的新 AtomicStampedReference。
      * @param args
      */
     //創建AtomicStampedReference,用戶的賬戶是19,版本號是0
     static AtomicStampedReference<Integer> asr=new AtomicStampedReference<Integer>(19,0);
     public static void main(String[] args) {
         //創建3個線程給用戶充話費
         for(int i=0;i<3;i++){
              final int expectedStamp = asr.getStamp();//獲取時間戳
              new Thread(new Runnable() {
                  
                  @Override
                  public void run() {
                       while(true){
                            while(true){
                                //獲取值
                                Integer expectedReference = asr.getReference();
                                /**
                                 * 小於20元話費,那麽充值20
                                 */
                                 if(expectedReference<20){
                                     if(asr.compareAndSet(expectedReference, expectedReference+20, expectedStamp, expectedStamp+1)){
                                          System.out.println("充值成功,余額為:"+asr.getReference());
                                          break;
                                     }
                                }else{
                                     break;
                                }
                            }
                       }
                  }
              }).start();
         }
         //啟動100個線程消費
         new Thread(new Runnable() {
              
              @Override
              public void run() {
                  for(int i=0;i<100;i++){
                       while(true){
                            int stamp = asr.getStamp();
                            Integer reference = asr.getReference();
                            if(reference>10){
                                 if(asr.compareAndSet(reference, reference-10, stamp, stamp+1)){
                                      System.out.println("消費10元,余額:"+ asr.getReference());
                             break;
                                }
                            }else{
                                break;
                            }
                       }
                       try{
                             Thread.sleep(100);
                       }catch(Exception e){
                            
                       }
                  }
                  
              }
         }).start();
     }
}

解釋下代碼,有3個線程在給用戶充值,當用戶余額少於20時,就給用戶充值20元。有100個線程在消費,每次消費10元。用戶初始有9元,當使用AtomicStampedReference來實現時,只會給用戶充值一次,因為每次操作使得時間戳+1。運行結果:
充值成功,余額為:39
消費10元,余額:29
消費10元,余額:19
消費10元,余額:9

  

代碼修改:修改為AtomicReference類
package automic;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicTest2 {
     /**
      * AtomicStampedReference(V initialRef, int initialStamp)
      *   創建具有給定初始值的新 AtomicStampedReference。
      * @param args
      */
     //創建AtomicStampedReference,用戶的賬戶是19,版本號是0
     static AtomicReference<Integer> asr=new AtomicReference<Integer>(19);
     public static void main(String[] args) {
         //創建3個線程給用戶充話費
         for(int i=0;i<3;i++){
              new Thread(new Runnable() {
                  
                  @Override
                  public void run() {
                       while(true){
                            while(true){
                                //獲取值
                                Integer expectedReference = asr.get();
                                /**
                                 * 小於20元話費,那麽充值20
                                 */
                                 if(expectedReference<20){
                                     if(asr.compareAndSet(expectedReference, expectedReference+20)){
                                          System.out.println("充值成功,余額為:"+asr.get());
                                          break;
                                     }
                                }else{
                                     break;
                                }
                            }
                       }
                  }
              }).start();
         }
         //啟動100個線程消費
         new Thread(new Runnable() {
              
              @Override
              public void run() {
                  for(int i=0;i<100;i++){
                       while(true){
                            Integer reference = asr.get();
                            if(reference>10){
                                 if(asr.compareAndSet(reference, reference-10)){
                                      System.out.println("消費10元,余額:"+ asr.get());
                             break;
                                }
                            }else{
                                break;
                            }
                       }
                       try{
                             Thread.sleep(100);
                       }catch(Exception e){
                            
                       }
                  }
                  
              }
         }).start();
     }
}

運行結果:出現了多次充值的現象:
充值成功,余額為:39
消費10元,余額:29
消費10元,余額:19
充值成功,余額為:39
消費10元,余額:29
消費10元,余額:19
充值成功,余額為:39
消費10元,余額:29
充值成功,余額為:39
消費10元,余額:39
消費10元,余額:29
充值成功,余額為:39
消費10元,余額:39

並發之無鎖技術歸納