国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

[Java并發(fā)-12] 原子類(lèi):無(wú)鎖工具類(lèi)的典范

h9911 / 3430人閱讀

摘要:并發(fā)包將這種無(wú)鎖方案封裝提煉之后,實(shí)現(xiàn)了一系列的原子類(lèi)。無(wú)鎖方案相對(duì)互斥鎖方案,最大的好處就是性能。作為一條指令,指令本身是能夠保證原子性的。

前面我們多次提到一個(gè)累加器的例子,示例代碼如下。在這個(gè)例子中,add10K() 這個(gè)方法不是線(xiàn)程安全的,問(wèn)題就出在變量 count 的可見(jiàn)性和 count+=1 的原子性上??梢?jiàn)性問(wèn)題可以用 volatile 來(lái)解決,而原子性問(wèn)題我們前面一直都是采用的互斥鎖方案。

public class Test {
  long count = 0;
  void add10K() {
    int idx = 0;
    while(idx++ < 10000) {
      count += 1;
    }
  }
}

其實(shí)對(duì)于簡(jiǎn)單的原子性問(wèn)題,還有一種無(wú)鎖方案。Java SDK 并發(fā)包將這種無(wú)鎖方案封裝提煉之后,實(shí)現(xiàn)了一系列的原子類(lèi)。

在下面的代碼中,我們將原來(lái)的 long 型變量 count 替換為了原子類(lèi) AtomicLong,原來(lái)的count +=1 替換成了 count.getAndIncrement(),僅需要這兩處簡(jiǎn)單的改動(dòng)就能使 add10K() 方法變成線(xiàn)程安全的,原子類(lèi)的使用還是挺簡(jiǎn)單的。

public class Test {
  AtomicLong count = 
    new AtomicLong(0);
  void add10K() {
    int idx = 0;
    while(idx++ < 10000) {
      count.getAndIncrement();
    }
  }
}

無(wú)鎖方案相對(duì)互斥鎖方案,最大的好處就是性能?;コ怄i方案為了保證互斥性,需要執(zhí)行加鎖、解鎖操作,而加鎖、解鎖操作本身就消耗性能;同時(shí)拿不到鎖的線(xiàn)程還會(huì)進(jìn)入阻塞狀態(tài),進(jìn)而觸發(fā)線(xiàn)程切換,線(xiàn)程切換對(duì)性能的消耗也很大。 相比之下,無(wú)鎖方案則完全沒(méi)有加鎖、解鎖的性能消耗,同時(shí)還能保證互斥性,既解決了問(wèn)題,又沒(méi)有帶來(lái)新的問(wèn)題,可謂絕佳方案。那它是如何做到的呢?

無(wú)鎖方案的實(shí)現(xiàn)原理

其實(shí)原子類(lèi)性能高的秘密很簡(jiǎn)單,硬件支持而已。CPU 為了解決并發(fā)問(wèn)題,提供了 CAS 指令(CAS,全稱(chēng)是 Compare And Swap,即“比較并交換”)。CAS 指令包含 3 個(gè)參數(shù):共享變量的內(nèi)存地址 A、用于比較的值 B 和共享變量的新值 C;并且只有當(dāng)內(nèi)存中地址 A 處的值等于 B 時(shí),才能將內(nèi)存中地址 A 處的值更新為新值 C。作為一條 CPU 指令,CAS 指令本身是能夠保證原子性的。

你可以通過(guò)下面 CAS 指令的模擬代碼來(lái)理解 CAS 的工作原理。在下面的模擬程序中有兩個(gè)參數(shù),一個(gè)是期望值 expect,另一個(gè)是需要寫(xiě)入的新值 newValue, 只有當(dāng)目前 count 的值和期望值 expect 相等時(shí),才會(huì)將 count 更新為 newValue

class SimulatedCAS{
  int count;
  synchronized int cas(
    int expect, int newValue){
    // 讀目前 count 的值
    int curValue = count;
    // 比較目前 count 值是否 == 期望值
    if(curValue == expect){
      // 如果是,則更新 count 的值
      count = newValue;
    }
    // 返回寫(xiě)入前的值
    return curValue;
  }
}

count += 1的一個(gè)核心問(wèn)題是:基于內(nèi)存中 count 的當(dāng)前值 A 計(jì)算出來(lái)的 count+=1 為 A+1,在將 A+1 寫(xiě)入內(nèi)存的時(shí)候,很可能此時(shí)內(nèi)存中 count 已經(jīng)被其他線(xiàn)程更新過(guò)了,這樣就會(huì)導(dǎo)致錯(cuò)誤地覆蓋其他線(xiàn)程寫(xiě)入的值。

使用 CAS 來(lái)解決并發(fā)問(wèn)題,一般都會(huì)伴隨著自旋,而所謂自旋,其實(shí)就是循環(huán)嘗試。例如,實(shí)現(xiàn)一個(gè)線(xiàn)程安全的count += 1操作,CAS+ 自旋的實(shí)現(xiàn)方案如下所示,首先計(jì)算 newValue = count+1,如果 cas(count,newValue) 返回的值不等于 count,則意味著線(xiàn)程在執(zhí)行完代碼①處之后,執(zhí)行代碼②處之前,count 的值被其他線(xiàn)程更新過(guò)。那此時(shí)該怎么處理呢?可以采用自旋方案,就像下面代碼中展示的,可以重新讀 count 最新的值來(lái)計(jì)算 newValue 并嘗試再次更新,直到成功。

class SimulatedCAS{
  volatile int count;
  // 實(shí)現(xiàn) count+=1
  addOne(){
    do {
      newValue = count+1; //①
    }while(count !=
      cas(count,newValue) //②
  }
  // 模擬實(shí)現(xiàn) CAS,僅用來(lái)幫助理解
  synchronized int cas(
    int expect, int newValue){
    // 讀目前 count 的值
    int curValue = count;
    // 比較目前 count 值是否 == 期望值
    if(curValue == expect){
      // 如果是,則更新 count 的值
      count= newValue;
    }
    // 返回寫(xiě)入前的值
    return curValue;
  }
}

通過(guò)上面的示例代碼,想必你已經(jīng)發(fā)現(xiàn)了,CAS 這種無(wú)鎖方案,完全沒(méi)有加鎖、解鎖操作,即便兩個(gè)線(xiàn)程完全同時(shí)執(zhí)行 addOne() 方法,也不會(huì)有線(xiàn)程被阻塞,所以相對(duì)于互斥鎖方案來(lái)說(shuō),性能好了很多。

但是在 CAS 方案中,有一個(gè)問(wèn)題可能會(huì)常被你忽略,那就是ABA問(wèn)題。

前面我們提到“如果 cas(count,newValue) 返回的值不等于count,意味著線(xiàn)程在執(zhí)行完代碼①處之后,執(zhí)行代碼②處之前,count 的值被其他線(xiàn)程更新過(guò)。那如果 cas(count,newValue) 返回的值等于count,是否就能夠認(rèn)為 count 的值沒(méi)有被其他線(xiàn)程更新過(guò)呢?

顯然不是的,假設(shè) count 原本是 A,線(xiàn)程 T1 在執(zhí)行完代碼①處之后,執(zhí)行代碼②處之前,有可能 count 被線(xiàn)程 T2 更新成了 B,之后又被 T3 更新回了 A,這樣線(xiàn)程 T1 雖然看到的一直是 A,但是其實(shí)已經(jīng)被其他線(xiàn)程更新過(guò)了,這就是 ABA 問(wèn)題。

可能大多數(shù)情況下我們并不關(guān)心 ABA 問(wèn)題,例如數(shù)值的原子遞增,但也不能所有情況下都不關(guān)心,例如原子化的更新對(duì)象很可能就需要關(guān)心 ABA 問(wèn)題,因?yàn)閮蓚€(gè) A 雖然相等,但是第二個(gè) A 的屬性可能已經(jīng)發(fā)生變化了。所以在使用 CAS 方案的時(shí)候,一定要先 check 一下。

Java 如何實(shí)現(xiàn)原子化的 count += 1

在本文開(kāi)始部分,我們使用原子類(lèi) AtomicLong 的 getAndIncrement() 方法替代了count+1

1,從而實(shí)現(xiàn)了線(xiàn)程安全。原子類(lèi) AtomicLong 的 getAndIncrement() 方法內(nèi)部就是基于 CAS 實(shí)現(xiàn)的,下面我們來(lái)看看 Java 是如何使用 CAS 來(lái)實(shí)現(xiàn)原子化的

在 Java 1.8 版本中,getAndIncrement() 方法會(huì)轉(zhuǎn)調(diào) unsafe.getAndAddLong() 方法。這里 this 和 valueOffset 兩個(gè)參數(shù)可以唯一確定共享變量的內(nèi)存地址。

final long getAndIncrement() {
  return unsafe.getAndAddLong(
    this, valueOffset, 1L);
}

unsafe.getAndAddLong() 方法的源碼如下,該方法首先會(huì)在內(nèi)存中讀取共享變量的值,之后循環(huán)調(diào)用 compareAndSwapLong() 方法來(lái)嘗試設(shè)置共享變量的值,直到成功為止。compareAndSwapLong() 是一個(gè) native 方法,只有當(dāng)內(nèi)存中共享變量的值等于 expected 時(shí),才會(huì)將共享變量的值更新為 x,并且返回 true;否則返回 fasle。compareAndSwapLong 的語(yǔ)義和 CAS 指令的語(yǔ)義的差別僅僅是返回值不同而已。

public final long getAndAddLong(
  Object o, long offset, long delta){
  long v;
  do {
    // 讀取內(nèi)存中的值
    v = getLongVolatile(o, offset);
  } while (!compareAndSwapLong(
      o, offset, v, v + delta));
  return v;
}
// 原子性地將變量更新為 x
// 條件是內(nèi)存中的值等于 expected
// 更新成功則返回 true
native boolean compareAndSwapLong(
  Object o, long offset, 
  long expected,
  long x);

另外,需要你注意的是,getAndAddLong() 方法的實(shí)現(xiàn),基本上就是 CAS 使用的經(jīng)典范例。所以請(qǐng)你再次體會(huì)下面這段抽象后的代碼片段,它在很多無(wú)鎖程序中經(jīng)常出現(xiàn)。Java 提供的原子類(lèi)里面 CAS 一般被實(shí)現(xiàn)為 compareAndSet(),compareAndSet() 的語(yǔ)義和 CAS 指令的語(yǔ)義的差別僅僅是返回值不同而已,compareAndSet() 里面如果更新成功,則會(huì)返回 true,否則返回 false。

do {
  // 獲取當(dāng)前值
  oldV = xxxx;
  // 根據(jù)當(dāng)前值計(jì)算新值
  newV = ...oldV...
}while(!compareAndSet(oldV,newV);
原子類(lèi)概覽

Java SDK 并發(fā)包里提供的原子類(lèi)內(nèi)容很豐富,我們可以將它們分為五個(gè)類(lèi)別:

原子化的基本數(shù)據(jù)類(lèi)型

原子化的對(duì)象引用類(lèi)型

原子化數(shù)組

原子化對(duì)象屬性更新器

原子化的累加器

。這五個(gè)類(lèi)別提供的方法基本上是相似的,并且每個(gè)類(lèi)別都有若干原子類(lèi),你可以通過(guò)下面的原子類(lèi)組成概覽圖來(lái)獲得一個(gè)全局的印象。下面我們?cè)敿?xì)解讀這五個(gè)類(lèi)別。

1. 原子化的基本數(shù)據(jù)類(lèi)型

相關(guān)實(shí)現(xiàn)有 AtomicBoolean、AtomicInteger 和 AtomicLong,提供的方法主要有以下這些,詳情你可以參考 SDK 的源代碼,都很簡(jiǎn)單,這里就不詳細(xì)介紹了。

getAndIncrement() // 原子化 i++
getAndDecrement() // 原子化的 i--
incrementAndGet() // 原子化的 ++i
decrementAndGet() // 原子化的 --i
// 當(dāng)前值 +=delta,返回 += 前的值
getAndAdd(delta) 
// 當(dāng)前值 +=delta,返回 += 后的值
addAndGet(delta)
//CAS 操作,返回是否成功
compareAndSet(expect, update)
// 以下四個(gè)方法
// 新值可以通過(guò)傳入 func 函數(shù)來(lái)計(jì)算
getAndUpdate(func)
updateAndGet(func)
getAndAccumulate(x,func)
accumulateAndGet(x,func)
2. 原子化的對(duì)象引用類(lèi)型

相關(guān)實(shí)現(xiàn)有 AtomicReference、AtomicStampedReference 和 AtomicMarkableReference,利用它們可以實(shí)現(xiàn)對(duì)象引用的原子化更新。AtomicReference 提供的方法和原子化的基本數(shù)據(jù)類(lèi)型差不多,這里不再贅述。不過(guò)需要注意的是,對(duì)象引用的更新需要重點(diǎn)關(guān)注 ABA 問(wèn)題,AtomicStampedReference 和 AtomicMarkableReference 這兩個(gè)原子類(lèi)可以解決 ABA 問(wèn)題。

解決 ABA 問(wèn)題的思路其實(shí)很簡(jiǎn)單,增加一個(gè)版本號(hào)維度就可以了。每次執(zhí)行 CAS 操作,附加再更新一個(gè)版本號(hào),只要保證版本號(hào)是遞增的,那么即便 A 變成 B 之后再變回 A,版本號(hào)也不會(huì)變回來(lái)(版本號(hào)遞增的)。AtomicStampedReference 實(shí)現(xiàn)的 CAS 方法就增加了版本號(hào)參數(shù),方法簽名如下:

boolean compareAndSet(
  V expectedReference,
  V newReference,
  int expectedStamp,
  int newStamp) 

AtomicMarkableReference 的實(shí)現(xiàn)機(jī)制則更簡(jiǎn)單,將版本號(hào)簡(jiǎn)化成了一個(gè) Boolean 值,方法簽名如下:

boolean compareAndSet(
  V expectedReference,
  V newReference,
  boolean expectedMark,
  boolean newMark)
3. 原子化數(shù)組

相關(guān)實(shí)現(xiàn)有 AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray,利用這些原子類(lèi),我們可以原子化地更新數(shù)組里面的每一個(gè)元素。這些類(lèi)提供的方法和原子化的基本數(shù)據(jù)類(lèi)型的區(qū)別僅僅是:每個(gè)方法多了一個(gè)數(shù)組的索引參數(shù),所以這里也不再贅述了。

4. 原子化對(duì)象屬性更新器

相關(guān)實(shí)現(xiàn)有 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater,利用它們可以原子化地更新對(duì)象的屬性,這三個(gè)方法都是利用反射機(jī)制實(shí)現(xiàn)的,創(chuàng)建更新器的方法如下:

public static 
AtomicXXXFieldUpdater 
newUpdater(Class tclass, 
  String fieldName)

需要注意的是,對(duì)象屬性必須是 volatile 類(lèi)型的,只有這樣才能保證可見(jiàn)性;如果對(duì)象屬性不是 volatile 類(lèi)型的,newUpdater() 方法會(huì)拋出 IllegalArgumentException 這個(gè)運(yùn)行時(shí)異常。

你會(huì)發(fā)現(xiàn) newUpdater() 的方法參數(shù)只有類(lèi)的信息,沒(méi)有對(duì)象的引用,而更新對(duì)象的屬性,一定需要對(duì)象的引用,那這個(gè)參數(shù)是在哪里傳入的呢?是在原子操作的方法參數(shù)中傳入的。例如 compareAndSet() 這個(gè)原子操作,相比原子化的基本數(shù)據(jù)類(lèi)型多了一個(gè)對(duì)象引用 obj。原子化對(duì)象屬性更新器相關(guān)的方法,相比原子化的基本數(shù)據(jù)類(lèi)型僅僅是多了對(duì)象引用參數(shù),所以這里也不再贅述了。

boolean compareAndSet(
  T obj, 
  int expect, 
  int update)
5. 原子化的累加器

DoubleAccumulator、DoubleAdder、LongAccumulator 和 LongAdder,這四個(gè)類(lèi)僅僅用來(lái)執(zhí)行累加操作,相比原子化的基本數(shù)據(jù)類(lèi)型,速度更快,但是不支持 compareAndSet() 方法。如果你僅僅需要累加操作,使用原子化的累加器性能會(huì)更好。

總結(jié)

無(wú)鎖方案相對(duì)于互斥鎖方案,優(yōu)點(diǎn)非常多,首先性能好,其次是基本不會(huì)出現(xiàn)死鎖問(wèn)題(但可能出現(xiàn)饑餓和活鎖問(wèn)題,因?yàn)樽孕龝?huì)反復(fù)重試)。Java 提供的原子類(lèi)大部分都實(shí)現(xiàn)了 compareAndSet() 方法。

Java 提供的原子類(lèi)能夠解決一些簡(jiǎn)單的原子性問(wèn)題,但你可能會(huì)發(fā)現(xiàn),上面我們所有原子類(lèi)的方法都是針對(duì)一個(gè)共享變量的,如果你需要解決多個(gè)變量的原子性問(wèn)題,建議還是使用互斥鎖方案。原子類(lèi)雖好,但使用要慎之又慎。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/77776.html

相關(guān)文章

  • Java多線(xiàn)程進(jìn)階(一)—— J.U.C并發(fā)包概述

    摘要:整個(gè)包,按照功能可以大致劃分如下鎖框架原子類(lèi)框架同步器框架集合框架執(zhí)行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據(jù)一系列常見(jiàn)的多線(xiàn)程設(shè)計(jì)模式,設(shè)計(jì)了并發(fā)包,其中包下提供了一系列基礎(chǔ)的鎖工具,用以對(duì)等進(jìn)行補(bǔ)充增強(qiáng)。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發(fā)于一世流云專(zhuān)欄:https...

    anonymoussf 評(píng)論0 收藏0
  • Java并發(fā)基礎(chǔ):了解無(wú)鎖CAS就從源碼分析

    摘要:該類(lèi)將整數(shù)值與引用關(guān)聯(lián)起來(lái),可用于原子的更數(shù)據(jù)和數(shù)據(jù)的版本號(hào)。 CAS的全稱(chēng)為Compare And Swap,直譯就是比較交換。是一條CPU的原子指令,其作用是讓CPU先進(jìn)行比較兩個(gè)值是否相等,然后原子地更新某個(gè)位置的值,其實(shí)現(xiàn)方式是基于硬件平臺(tái)的匯編指令,在intel的CPU中,使用的是cmpxchg指令,就是說(shuō)CAS是靠硬件實(shí)現(xiàn)的,從而在硬件層面提升效率。 CSA 原理 利用CP...

    toddmark 評(píng)論0 收藏0
  • 【實(shí)戰(zhàn)Java并發(fā)程序設(shè)計(jì)5】讓普通變量也享受原子操作

    摘要:有時(shí)候,由于初期考慮不周,或者后期的需求變化,一些普通變量可能也會(huì)有線(xiàn)程安全的需求。它可以讓你在不改動(dòng)或者極少改動(dòng)原有代碼的基礎(chǔ)上,讓普通的變量也享受操作帶來(lái)的線(xiàn)程安全性,這樣你可以修改極少的代碼,來(lái)獲得線(xiàn)程安全的保證。 有時(shí)候,由于初期考慮不周,或者后期的需求變化,一些普通變量可能也會(huì)有線(xiàn)程安全的需求。如果改動(dòng)不大,我們可以簡(jiǎn)單地修改程序中每一個(gè)使用或者讀取這個(gè)變量的地方。但顯然,這...

    appetizerio 評(píng)論0 收藏0
  • 無(wú)鎖算法

    摘要:無(wú)鎖算法在并發(fā)編程中,提供了很多并發(fā)編程工具類(lèi)。在包下有一個(gè)包叫,下面所有的工具,我們都稱(chēng)為它是無(wú)鎖算法的一種實(shí)現(xiàn)。相對(duì)于有鎖算法來(lái)說(shuō),無(wú)鎖算法不會(huì)使等待線(xiàn)程休眠或者阻塞。 無(wú)鎖算法 在Java并發(fā)編程中,Java提供了很多并發(fā)編程工具類(lèi)。在JUC包下有一個(gè)包叫atomic,下面所有的工具,我們都稱(chēng)為它是無(wú)鎖算法的一種實(shí)現(xiàn)。 相對(duì)于有鎖算法來(lái)說(shuō),無(wú)鎖算法不會(huì)使等待線(xiàn)程休眠或者阻塞。它的...

    hover_lew 評(píng)論0 收藏0
  • 【實(shí)戰(zhàn)Java并發(fā)程序設(shè)計(jì)6】挑戰(zhàn)無(wú)鎖算法

    摘要:在本例中,講述的無(wú)鎖來(lái)自于并發(fā)包我們將這個(gè)無(wú)鎖的稱(chēng)為。在這里,我們使用二維數(shù)組來(lái)表示的內(nèi)部存儲(chǔ),如下變量存放所有的內(nèi)部元素。為什么使用二維數(shù)組去實(shí)現(xiàn)一個(gè)一維的呢這是為了將來(lái)進(jìn)行動(dòng)態(tài)擴(kuò)展時(shí)可以更加方便。 我們已經(jīng)比較完整得介紹了有關(guān)無(wú)鎖的概念和使用方法。相對(duì)于有鎖的方法,使用無(wú)鎖的方式編程更加考驗(yàn)一個(gè)程序員的耐心和智力。但是,無(wú)鎖帶來(lái)的好處也是顯而易見(jiàn)的,第一,在高并發(fā)的情況下,它比有鎖...

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

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

0條評(píng)論

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