原子變量與synchronized詳細解釋

AtomicInteger,一個提供原子操做的Integer的類。在Java語言中,++i和i++操做並非線程安全的,在使用的時候,不可避免的會用到synchronized關鍵字。而AtomicInteger則經過一種線程安全的加減操做接口。 java

    要使用多處理器系統的功能,一般須要使用多線程構造應用程序。可是正如任何編寫併發應用程序的人能夠告訴你的那樣,要得到好的硬件利用率,只是簡單地在多個線程中分割工做是不夠的,還必須確保線程確實大部分時間都在工做,而不是在等待更多的工做,或等待鎖定共享數據結構。而synchronized來控制併發就須要去等待這個鎖資源,這步是很是消耗資源的,處理的吞吐量也就下去了。而java的concurrent 併發包提供的AtomicInteger就提供CAS原理避免了鎖等待,具體的實現是經過UnSafe類來直接操做底層的硬件資源。 
算法

cpu 硬件同步原語(compare and swap) 

支持併發的第一個處理器提供原子的測試並設置操做,一般在單位上運行這項操做。如今的處理器(包括 Intel 和 Sparc 處理器)使用的最通用的方法是實現名爲 比較並轉換或 CAS 的原語。(在 Intel 處理器中,比較並交換經過指令的 cmpxchg 系列實現。PowerPC 處理器有一對名爲「加載並保留」和「條件存儲」的指令,它們實現相同的目地;MIPS 與 PowerPC 處理器類似,除了第一個指令稱爲「加載連接」。) 

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

  一般將 CAS 用於同步的方式是從地址 V 讀取值 A,執行多步計算來得到新值 B,而後使用 CAS 將 V 的值從 A 改成 B。若是 V 處的值還沒有同時更改,則 CAS 操做成功。 

  相似於 CAS 的指令容許算法執行讀-修改-寫操做,而無需懼怕其餘線程同時修改變量,由於若是其餘線程修改變量,那麼 CAS 會檢測它(並失敗),算法能夠對該操做從新計算。CAS 操做的行爲(而不是性能特徵),可是 CAS 的價值是它能夠在硬件中實現,而且是極輕量級的(在大多數處理器中)。
安全

普通的類: 數據結構

public class SynchronizedCounter {
    private int value;

    public synchronized int getValue(){
        return value;
    }

    public synchronized int getNextValue(){
        return value++;
    }

    public synchronized int getPreviousValue(){
        return value--;
    }
}
增長synchronized關鍵字的類:

public class SynchronizedCounter {
    private int value;

    public synchronized int getValue(){
        return value;
    }

    public synchronized int getNextValue(){
        return value++;
    }

    public synchronized int getPreviousValue(){
        return value--;
    }
}
使用原子變量的類:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private final AtomicInteger value = new AtomicInteger(0);

    public int getValue(){
        return value.get();
    }

    public int getNextValue(){
        return value.incrementAndGet();
    }

    public int getPreviousValue(){
        return value.decrementAndGet();
    }
}
測試類:

public class Test {

	static SynchronizedCounter counteSynchronizedCounter  = new SynchronizedCounter();
	static AtomicCounter atomicCounter = new AtomicCounter();
	
	public static long startTime = 0;
	public static long endTime = 0;
	
	public static void main(String[] args) {
		startTime = System.currentTimeMillis();
		for (int i = 0; i < 1 ; i++) {
			Thread t = new Thread(runnable);
			t.start();
		}
	}
	
	static Runnable runnable = new Runnable() {
		@Override
		public void run() {
			for(int i=0;i<100000000;i++){
				counteSynchronizedCounter.getNextValue();
			}
			endTime = System.currentTimeMillis();
			System.out.println(endTime-startTime);
		}
	};
//開三個線程用時56936,開一個線程4368
//	static Runnable runnable = new Runnable() {
//		@Override
//		public void run() {
//			for(int i=0;i<100000000;i++){
//				atomicCounter.getNextValue();
//			}
//			endTime = System.currentTimeMillis();
//			System.out.println(endTime-startTime);
//		}
//	};
//開三個線程用時13323,一個線程用時2288
	

}
上面的結果說明了原子變量比synchronized關鍵字的運行效率高多了。

這裏解釋一下, 「相似於 CAS 的指令容許算法執行讀-修改-寫操做,而無需懼怕其餘線程同時修改變量,由於若是其餘線程修改變量,那麼 CAS 會檢測它(並失敗),算法能夠對該操做從新計算。」 多線程

 CAS 操做包含三個操做數 —— 內存位置(V)、預期原值(A)和新值(B)。若是內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值。不然,處理器不作任何操做。而後對該操做從新計算,這裏解釋一下AtomicInteger是怎麼從新計算的 併發

AtomicInteger中的源碼: ide

這裏使用了一個無限循環,只要沒有正確返回數據就一直循環。
性能

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);
    }
相關文章
相關標籤/搜索