Volatile適合使用場景、synchronized和volatile比較、J.U.C之CAS、CAS介紹、CAS原理剖析、native關鍵詞、多CPU的CAS處理

2020-08-08 22:18:35

Volatile適合使用場景
a)對變數的寫入操作不依賴其當前值
    不滿足: number++. count=count*5等
    滿足: boolean變數、直接賦值的變數等
b)該變數沒有包含在具有其他變數的不變式中
    不滿足:不變式low<up
總結:變數真正獨立於其他變數和自己以前的值,在單獨使用的時候,適合用volatile

synchronized和volatile比較
a) volatile不需要加鎖,比synchronized更輕便,不會阻塞執行緒
b) synchronized既能保證可見性,又能保證原子性.而volatile只能保證可見性,無法保證原子性

與鎖相比, Volatile 變數是一種非常簡單但同時又非常脆弱的同步機制 機製 ,它在某些情況下將提供優於鎖的效能和伸縮性。如果嚴格遵循volatile 的使用條件(變數真正獨立於其他變數和自己以前的值)在某些情況下可以使用volatile代替synchronized來優化程式碼提升效率。

J.U.C之CAS
J.U.C即java.util.concurrent ,是JISR 166標準規範的一個實現: JSR 166以及J.U.C包的作者是Doug Lea。

J.U.C框架是Java 5中引入的.而我們最熟悉的執行緒池機制 機製就在這個包, J.U.C框架包含的內容有:
●AbstractQueuedSynchronizer ( AQS框架) , J.U.C中實現鎖和同步機制 機製的基礎;
●Locks & Condition (鎖和條件變數) ,比synchronized, walt、 notify 更細粒度的鎖機制 機製;
●Executor框架(執行緒池、Callable、 Future) ,任務的執行和排程框架;
●Synchronizers (同步器) ,主要用於協助執行緒同步,有CountDownLatch、CyclicBarrier、Semaphore、Exchanger ;
●Atomic Variables (原子變數) , 方便程式設計師在多執行緒環境下,無鎖的進行原子操作,核心操作是CAS原子操作,所謂的CAS操作,即compare and swap ,指的是將預期值與當前變數的值比較(compare) ,如果相等則使用新值替換(swap)當前變數,否則不作操作;
●BlockingQueue (阻塞佇列) , 阻塞佇列提供了可阻塞的入隊和出隊操作,如果佇列滿了,入隊操作將阻塞直到有空間可用,如果佇列空了,出隊操作將阻塞直到有元素可用;
●Concurrent Collections (併發容器) ,說到併發容器,不得不提同步容器。在JDK1.5之前,爲了執行緒安全,我們一般都是使用同步容器,同步容器主要的缺點是:對所有容器狀態的存取都序列化,嚴重降低了併發性;某些複合操作,仍然需要加鎖來保護;迭代期間,若其它執行緒併發修改該容器,會拋出ConcurrentModificationException異常。即快速失敗機制 機製;
●Fork/Join並行計算框架.這塊內容是在JDK1.7中引入的.可以方便利用多核平臺的計算能力,簡化並行程式的編寫,開發人員僅需關注如何劃分任務和組合中間結果;

CAS介紹
CAS , Compare And Swap ,即比較並交換。同步元件中大量使用CAS技術實現了Java多執行緒的併發操作。整個AQS同步元件、Atomic原子類操作等等都是以CAS實現的,甚至ConcurrentHashMap在1.8的版本中也調整爲了CAS+Synchronized。可以說CAS是整個JUC的基石。

    Lock    同步器    阻塞佇列        執行器    併發容器
       ↑↑↑     ↑↑↑         ↑↑↑
        AQS        非阻塞數據結構    原子變數類
            ↑↑↑           ↑↑↑    
        volatile變數的讀/寫    CAS

CAS原理剖析

再次測試之前Volatile的例子,把回圈的次數調整爲一億(保證在一秒之內不能遍歷完成,從而測試三種原子操作的效能) , 我們發現, AtomicInteger原子操作效能最高,他是用的就是CAS

synchronized同步分析
注意,本小節是解釋synchronized效能低效的原因,只要能理解synchronized同步過程其實還需要做很多事,這些邏輯的執行都需要佔用資源,從而導致效能較低,是爲了對比CAS的高效。這部分分析過於深入MM底層原理,不適合初級甚至中級程式設計師學習。
我們之前講過, synchronized的同步操作主要是monitorenter和monitorexit這兩個jvm指令實現的,我們先寫一段簡單的程式碼:
public class Demo2Synchronized {
    public void test2() {
        synchronized (this) {
    }
}

CAS原理
在上一部分,我們介紹了synchronized底層做了大量的工作,才實現同步,而同步保證了原子操作。但是不可避免的是效能較低。CAS是如何提高效能的呢?
CAS的思想很簡單:三個參數, -一個當前記憶體值V、舊的預期值A、即將更新的值B ,當且僅當舊的預期值A和的存值V相同時,將記憶體值修改爲B並返回true ,否則什麼都不做,並返回false.如果CAS操作失敗,通過自旋的方式等待並再次嘗試,直到成功。
CAS在先比較後修改這個CAS過程中,根本沒有獲取鎖,釋放鎖的操作,是硬體層面的原子操作,跟JMM內存模型沒有關係。大家可以理解爲直接使用其他的語言,在JVM虛擬機器之外直接操作計算機硬體,正因爲如此,對比synchronized的同步, 少了很多的邏輯步驟,使得效能大爲提高。
jUC下的atomic類都是通過CAS來實現的,下面 下麪就是一個AtomicInteger原子操作類的例子 ,在其中使用了Unsafe unsafe = Unsafe.getUnsafe()。Unsafe 是CAS的核心類,它提供了硬體級別的原子操作。

private static final Unsafe unsafe = Unsafe. getUnsafe();
    private static final long value0ffset;
        static {
            try {
                value0ffset = unsafe . objectFieldoffset
                (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
        //操作的值也進行了volatile修飾,保證記憶體可見性
        private volatile int valuey;


繼續檢視AtomicInteger的addAndGet()方法:
pub1ic final int addAndGet(int delta) {
    return unsafe. get AndAddInt(this, value0ffset, delta) + delta;
}
public final int getAndAddInt (object var1, long var2,int var4) {
    int var5;
    do {
        var5 = this. getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1,var2,var5,var5 + var4));
    return var5;
}


native關鍵詞
前面提到了sun.misc.Unsafe這個類,裏面的方法使用native關鍵詞宣告本地方法.爲什麼要用native ?
Java無法直接存取底層操作系統,但有能力呼叫其他語言編寫的函數or方法,是通過JNI(Java Native
Interfface)實現。使用時,通過native關鍵字告訴JVM這個方法是在外部定義的。但JVM也不知道去哪找這個原生方法,此時需要通過javah命令生成.h檔案。
範例步驟(c語言爲例) :
    1.javac生成.class檔案.比如javac NativePeer.java
    2.javah生成h檔案,比如javah NativePeer
    3.編寫c語言檔案,在其中include上進一步生成的.h檔案,然後實現其中宣告而未實現的函數
    4.生成dll共用庫,然後Java程式load庫,呼叫即可

native可以和任何除abstract外的關鍵字連用。這也說明了這些方法是有實體的.並且能夠和其他Java方法一樣,擁有各種Java的特性。

    native方法有效地擴充了jvm ,實際上我們所用的很多程式碼已經涉及到這種方法了,通過非常        簡潔的介面幫我們實現Java以外的工作。

native優勢:
1.很多層次上用Java去實現是很麻煩的.而且Java解釋執行的效率也差了c語言啥的很多,純Java實現可能會導致效率不達標,或者可讀性奇差。
2. Java畢竟不是一個完整的系統 ,它經常需要一些底層的支援 ,通過JNI和native method我們就可以實現jre與底層的互動,得到強大的底層操作系統的支援,使用一些ava本身沒有封裝的操作系統的特性。

多CPU的CAS處理
CAS可以保證一次的讀-改-寫操作是原子操作,在單處理器上該操作容易實現,但是在多處理器上實現就有點兒複雜了。
CPU提供了兩種方法來實現多處理器的原子操作,匯流排加鎖或者快取加鎖。

●匯流排加鎖:匯流排加鎖就是就是使用處理器提供的-一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那麼該處理器可以獨佔使用共用記憶體。但是這種處理方式顯得有點兒霸道,不厚道,他把CPU和記憶體之間的通訊鎖住了,在鎖定期間,其他處理器都不能其他記憶體地址的數據,其開銷有點兒大。

●快取加鎖:其實針對於上面那種情況我們只需要保證在同-一時刻對某個記憶體地址的操作是原子性的即可。快取加鎖就是快取在記憶體區域的數據如果在加鎖期間,當它執行鎖操作寫回記憶體時,處理器不在輸出LOCK#信號,而是修改內部的記憶體地址,利用快取一致性協定來保證原子性。 快取一致性機制 機製可以保證同一個記憶體區域的數據僅能被一個處理器修改,也就是說當CPU1修改快取行中的時使用快取鎖定,那麼CPU2就不能同時快取了i的快取行。