亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

淺談cas

琛h。 / 1409人閱讀

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

前言

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

問(wèn)題

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

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

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

}

通過(guò)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
}

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

解決方案

java給我們提供了一個(gè)關(guān)鍵字synchronized,可以對(duì)成員方法、靜態(tài)方法、代碼塊進(jìn)行加鎖,從而保證操作的原子性。但效率不高,還有其他辦法嗎?當(dāng)然有了,就是我們今天的主角cas。接下來(lái)我們?cè)賮?lái)看看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對(duì)象,long var2Integer對(duì)象的內(nèi)存偏移值,int var4 增加的值
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
        //從方法名字就可以看出,獲取線程可見(jiàn)的值
            var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
    
    }

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

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

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

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

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

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

intel的手冊(cè)對(duì)lock前綴的說(shuō)明如下:

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

cas的缺點(diǎn)

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

總結(jié)

自旋cas如果長(zhǎng)時(shí)間不成功,將會(huì)對(duì)cpu帶來(lái)非常大的開(kāi)銷(xiāo)。cas只能保證一個(gè)共享變量的原子操作。所以非常簡(jiǎn)單的操作又不想引入鎖,cas是一個(gè)非常好的選擇。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/68372.html

相關(guān)文章

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

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

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

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

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

    摘要:耐心看完的你或多或少會(huì)有收獲并發(fā)的核心就是包,而的核心是抽象隊(duì)列同步器,簡(jiǎn)稱(chēng),一些鎖啊信號(hào)量啊循環(huán)屏障啊都是基于。 耐心看完的你或多或少會(huì)有收獲! Java并發(fā)的核心就是 java.util.concurrent 包,而 j.u.c 的核心是AbstractQueuedSynchronizer抽象隊(duì)列同步器,簡(jiǎn)稱(chēng) AQS,一些鎖??!信號(hào)量?。⊙h(huán)屏障?。《际腔贏QS。而 AQS 又是...

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

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

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

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

    Null 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<