成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

淺談cas

摘要:在的包中,大神大量使用此技術(shù),實現(xiàn)了多線程的安全性。我們將變量用修飾,保證線程間的可見性。線程也通過此方法獲取當(dāng)前值,進(jìn)行操作,比較內(nèi)存值相等進(jìn)行修改。我們通過保證了對的并發(fā)線程安全,其安全的保證是通過調(diào)用的代碼實現(xiàn)的。

前言

研究java并發(fā)編程有一段時間了, 在并發(fā)編程中cas出現(xiàn)的次數(shù)極為頻繁。cas的英文全名叫做compare and swap,意思很簡單就是比較并交換。在jdk的conurrent包中,Doug Lea大神大量使用此技術(shù),實現(xiàn)了多線程的安全性。
cas的核心思想就是獲取當(dāng)前的內(nèi)存偏移值、期望值和更新值,如果根據(jù)內(nèi)存偏移值得到的變量等于期望值,則進(jìn)行更新。

問題

總有面試官喜歡問你i++和++i,以及經(jīng)典的字符串問題,其實這些問題只要你試用javap -c這個命令反編譯一下,就一目了然。當(dāng)然今天的主題是cas,我首先來研究下a++:

//@RunWith(SpringRunner.class)
//@SpringBootTest
public class SblearnApplicationTests {

    public static volatile  int a;
    public static void main(String[] args) {
        a++;
    }

}

通過javac SblearnApplicationTests.java,javap -c SblearnApplicationTests.class可以得到:

Compiled from "SblearnApplicationTests.java"
public class com.example.sblearn.SblearnApplicationTests {
  public static volatile int a;

  public com.example.sblearn.SblearnApplicationTests();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field a:I
       3: iconst_1                          //當(dāng)int取值-1~5采用iconst指令,取值-128~127采用bipush指令,取值-32768~32767采用sipush指令,取值-2147483648~2147483647采用 ldc 指令
       4: iadd
       5: putstatic     #2                  // Field a:I
       8: return
}

通過反編譯得出如上的結(jié)果,都是一些jvm的指令,百度一下就能知道意思。我們將變量a用violate修飾,保證線程間的可見性。通過jvm指令可知a++不是一個原子動作,如果多個線程同事對a進(jìn)行操作,無法保證線程安全,那怎么解決呢?

解決方案

java給我們提供了一個關(guān)鍵字synchronized,可以對成員方法、靜態(tài)方法、代碼塊進(jìn)行加鎖,從而保證操作的原子性。但效率不高,還有其他辦法嗎?當(dāng)然有了,就是我們今天的主角cas。接下來我們再來看看concurrent包下的AtomicInteger:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
    //效果等同于a++,但保證了原子性
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    }
public final class Unsafe {

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public native int getIntVolatile(Object var1, long var2);
    //object var1:當(dāng)前AtomicInteger對象,long var2Integer對象的內(nèi)存偏移值,int var4 增加的值
    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;
    }
    
    }

cas機(jī)制的核心類就是Unsafe,valueOffset 是其內(nèi)存偏移值。由于java語言無法直接操作底層,需要本地方法(native method)來訪問,unsafe這個類中存在大量本地方法,就是在調(diào)用c去操作特定內(nèi)存的數(shù)據(jù)。我們先假設(shè)unsafe幫我們保證了原子性,先來分析下AtomicInteger.getAndIncrement(),在jdk1.8中,其實現(xiàn)就是Unsafe.getAndAddInt()

現(xiàn)在我們假設(shè)有A、B線程同時來操作AtomicInteger,其初始值為1,根據(jù)java內(nèi)存模型,當(dāng)前主內(nèi)存AtomicInteger值為1,線程A、線程B各自的工作內(nèi)存也為1.

線程A獲得通過getIntVolatile獲取當(dāng)前值,被掛起。線程B也通過此方法獲取當(dāng)前值,進(jìn)行操作,比較內(nèi)存值相等進(jìn)行修改。

這時線程A恢復(fù),執(zhí)行compareAndSwapInt發(fā)現(xiàn)與內(nèi)存期望值不相等,重新獲取var5變量(因為被violate修飾,所以工作內(nèi)存和主內(nèi)存變量一致),再次比較與內(nèi)存期望值相等,進(jìn)行更新。

我們通過cas保證了對value的并發(fā)線程安全,其安全的保證是CAS通過調(diào)用JNI的代碼實現(xiàn)的。JNI:Java Native Interface為JAVA本地調(diào)用,允許java調(diào)用其他語言。而compareAndSwapInt就是借助C來調(diào)用CPU底層指令實現(xiàn)的。下面從分析比較常用的CPU(intel x86)來解釋CAS的實現(xiàn)原理。compareAndSwapInt方法在openjdk中依次調(diào)用的c++代碼為:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。這個本地方法的最終實現(xiàn)在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011openjdkhotspotsrcoscpuwindowsx86vm atomicwindowsx86.inline.hpp(對應(yīng)于windows操作系統(tǒng),X86處理器)。下面是對應(yīng)于intel x86處理器的源代碼的片段:

// Adding a lock prefix to an instruction on MP machine  
// VC++ doesn"t like the lock prefix to be on a single line  
// so we can"t insert a label after the lock prefix.  
// By emitting a lock prefix, we can define a label after it.  
#define LOCK_IF_MP(mp) __asm cmp mp, 0    
                       __asm je L0        
                       __asm _emit 0xF0   
                       __asm L0:  
  
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {  
  // alternative for InterlockedCompareExchange  
  int mp = os::is_MP();  
  __asm {  
    mov edx, dest  
    mov ecx, exchange_value  
    mov eax, compare_value  
    LOCK_IF_MP(mp)  
    cmpxchg dword ptr [edx], ecx  
  }  
}  

如上面源代碼所示,程序會根據(jù)當(dāng)前處理器的類型來決定是否為cmpxchg指令添加lock前綴。如果程序是在多處理器上運(yùn)行,就為cmpxchg指令加上lock前綴(lock cmpxchg)。反之,如果程序是在單處理器上運(yùn)行,就省略lock前綴(單處理器自身會維護(hù)單處理器內(nèi)的順序一致性,不需要lock前綴提供的內(nèi)存屏障效果)。

intel的手冊對lock前綴的說明如下:

1.確保對內(nèi)存的讀-改-寫操作原子執(zhí)行。在Pentium及Pentium之前的處理器中,帶有l(wèi)ock前綴的指令在執(zhí)行期間會鎖住總線,使得其他處理器暫時無法通過總線訪問內(nèi)存。很顯然,這會帶來昂貴的開銷。從Pentium 4,Intel Xeon及P6處理器開始,intel在原有總線鎖的基礎(chǔ)上做了一個很有意義的優(yōu)化:如果要訪問的內(nèi)存區(qū)域(area of memory)在lock前綴指令執(zhí)行期間已經(jīng)在處理器內(nèi)部的緩存中被鎖定(即包含該內(nèi)存區(qū)域的緩存行當(dāng)前處于獨(dú)占或以修改狀態(tài)),并且該內(nèi)存區(qū)域被完全包含在單個緩存行(cache line)中,那么處理器將直接執(zhí)行該指令。由于在指令執(zhí)行期間該緩存行會一直被鎖定,其它處理器無法讀/寫該指令要訪問的內(nèi)存區(qū)域,因此能保證指令執(zhí)行的原子性。這個操作過程叫做緩存鎖定(cache locking),緩存鎖定將大大降低lock前綴指令的執(zhí)行開銷,但是當(dāng)多處理器之間的競爭程度很高或者指令訪問的內(nèi)存地址未對齊時,仍然會鎖住總線。
2.禁止該指令與之前和之后的讀和寫指令重排序。
3.把寫緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中。

cas的缺點

cas的缺點就是會出現(xiàn)aba問題,假如一個字母為a,它經(jīng)歷a->b->a的過程,實際已經(jīng)改變兩次,但值相同。部分業(yè)務(wù)場景是不允許出現(xiàn)這種情況的(比如銀行轉(zhuǎn)賬..).解決辦法就是添加版本號,他就變成了1a->2b>3a。jdk1.5之后也提供了AtomicStampedReference來解決aba問題。

總結(jié)

自旋cas如果長時間不成功,將會對cpu帶來非常大的開銷。cas只能保證一個共享變量的原子操作。所以非常簡單的操作又不想引入鎖,cas是一個非常好的選擇。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68372.html

相關(guān)文章

  • 淺談Java并發(fā)編程系列(七) —— 深入解析synchronized關(guān)鍵字

    摘要:第一個字被稱為。經(jīng)量級鎖的加鎖過程當(dāng)一個對象被鎖定時,被復(fù)制到當(dāng)前嘗試獲取鎖的線程的線程棧的鎖記錄空間被復(fù)制的官方稱為。根據(jù)鎖對象目前是否處于被鎖定狀態(tài),撤銷偏向后恢復(fù)到未鎖定或經(jīng)量級鎖定狀態(tài)。 Synchronized關(guān)鍵字 synchronized的鎖機(jī)制的主要優(yōu)勢是Java語言內(nèi)置的鎖機(jī)制,因此,JVM可以自由的優(yōu)化而不影響已存在的代碼。 任何對象都擁有對象頭這一數(shù)據(jù)結(jié)構(gòu)來支持鎖...

    piglei 評論0 收藏0
  • 淺談java中的并發(fā)控制

    摘要:并發(fā)需要解決的問題功能性問題線程同步面臨兩個問題,想象下有兩個線程在協(xié)作工作完成某項任務(wù)。鎖可用于規(guī)定一個臨界區(qū),同一時間臨界區(qū)內(nèi)僅能由一個線程訪問。并發(fā)的數(shù)據(jù)結(jié)構(gòu)線程安全的容器,如等。 并發(fā)指在宏觀上的同一時間內(nèi)同時執(zhí)行多個任務(wù)。為了滿足這一需求,現(xiàn)代的操作系統(tǒng)都抽象出 線程 的概念,供上層應(yīng)用使用。 這篇博文不打算詳細(xì)展開分析,而是對java并發(fā)中的概念和工具做一個梳理。沿著并發(fā)模...

    Gilbertat 評論0 收藏0
  • Java并發(fā)核心淺談

    摘要:耐心看完的你或多或少會有收獲并發(fā)的核心就是包,而的核心是抽象隊列同步器,簡稱,一些鎖啊信號量啊循環(huán)屏障啊都是基于。 耐心看完的你或多或少會有收獲! Java并發(fā)的核心就是 java.util.concurrent 包,而 j.u.c 的核心是AbstractQueuedSynchronizer抽象隊列同步器,簡稱 AQS,一些鎖啊!信號量??!循環(huán)屏障??!都是基于AQS。而 AQS 又是...

    cppowboy 評論0 收藏0
  • 淺談Java中鎖的實現(xiàn)和優(yōu)化

    摘要:這兩種策略的區(qū)別就在于,公平策略會讓等待時間長的線程優(yōu)先執(zhí)行,非公平策略則是等待時間長的線程不一定會執(zhí)行,存在一個搶占資源的問題。 之前有一篇文章我們簡單的談到了Java中同步的問題,但是可能在平常的開發(fā)中,有些理論甚至是某些方式是用不到的,但是從程序的角度看,這些理論思想我們可以運(yùn)用到我們的開發(fā)中,比如是不是應(yīng)該一談到同步問題,就應(yīng)該想到用synchronized?,什么時候應(yīng)該用R...

    DevWiki 評論0 收藏0
  • Java并發(fā)核心淺談(二)

    摘要:在線程處理任務(wù)期間,其它線程要么循環(huán)訪問,要么一直阻塞等著線程喚醒,再不濟(jì)就真的如我所說,放棄鎖的競爭,去處理別的任務(wù)。寫鎖的話,獨(dú)占寫計數(shù),排除一切其他線程。 回顧 在上一篇 Java并發(fā)核心淺談 我們大概了解到了Lock和synchronized的共同點,再簡單總結(jié)下: Lock主要是自定義一個 counter,從而利用CAS對其實現(xiàn)原子操作,而synchronized是c++...

    Null 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<