熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

Java多線程之Atomic:原子變量與原子類

2022-06-13   來源: Java高級技術 

  何謂Atomic?

  Atomic一詞跟原子有點關系後者曾被人認為是最小物質的單位計算機中的Atomic是指不能分割成若干部分的意思如果一段代碼被認為是Atomic則表示這段代碼在執行過程中是不能被中斷的通常來說原子指令由硬件提供供軟件來實現原子方法(某個線程進入該方法後就不會被中斷直到其執行完成)

  在x 平台上CPU提供了在指令執行期間對總線加鎖的手段CPU芯片上有一條引線#HLOCK pin如果匯編語言的程序中在一條指令前面加上前綴LOCK經過匯編以後的機器代碼就使CPU在執行這條指令的時候把#HLOCK pin的電位拉低持續到這條指令結束時放開從而把總線鎖住這樣同一總線上別的CPU就暫時不能通過總線訪問內存了保證了這條指令在多處理器環境中的原子性

  ncurrent中的原子變量

  無論是直接的還是間接的幾乎 ncurrent 包中的所有類都使用原子變量而不使用同步類似 ConcurrentLinkedQueue 的類也使用原子變量直接實現無等待算法而類似 ConcurrentHashMap 的類使用 ReentrantLock 在需要時進行鎖定然後 ReentrantLock 使用原子變量來維護等待鎖定的線程隊列

  如果沒有 JDK 中的 JVM 改進將無法構造這些類這些改進暴露了(向類庫而不是用戶類)接口來訪問硬件級的同步原語然後ncurrent 中的原子變量類和其他類向用戶類公開這些功能

  ncurrentatomic的原子類

  這個包裡面提供了一組原子類其基本的特性就是在多線程環境下當有多個線程同時執行這些類的實例包含的方法時具有排他性即當某個線程進入方法執行其中的指令時不會被其他線程打斷而別的線程就像自旋鎖一樣一直等到該方法執行完成才由JVM從等待隊列中選擇一個另一個線程進入這只是一種邏輯上的理解實際上是借助硬件的相關指令來實現的不會阻塞線程(或者說只是在硬件級別上阻塞了)其中的類可以分成

  AtomicBooleanAtomicIntegerAtomicLongAtomicReference

  AtomicIntegerArrayAtomicLongArray

  AtomicLongFieldUpdaterAtomicIntegerFieldUpdaterAtomicReferenceFieldUpdater

  AtomicMarkableReferenceAtomicStampedReferenceAtomicReferenceArray

  其中AtomicBooleanAtomicIntegerAtomicLongAtomicReference是類似的

  首先AtomicBooleanAtomicIntegerAtomicLongAtomicReference內部api是類似的舉個AtomicReference的例子

  使用AtomicReference創建線程安全的堆棧

  Java代碼

  public class LinkedStack<T> {

  private AtomicReference<Node<T》 stacks = new AtomicReference<Node<T》()

  public T push(T e) {

  Node<T> oldNode newNode;

  while (true) { //這裡的處理非常的特別也是必須如此的

  oldNode = stacksget()

  newNode = new Node<T>(e oldNode)

  if (pareAndSet(oldNode newNode)) {

  return e;

  }

  }

  }

  public T pop() {

  Node<T> oldNode newNode;

  while (true) {

  oldNode = stacksget()

  newNode = oldNodenext;

  if (pareAndSet(oldNode newNode)) {

  return oldNodeobject;

  }

  }

  }

  private static final class Node<T> {

  private T object;

  private Node<T> next;

  private Node(T object Node<T> next) {

  thisobject = object;

  thisnext = next;

  }

  }

  }

  然後關注字段的原子更新

  AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<TV>是基於反射的原子更新字段的值

  相應的API也是非常簡

  單的但是也是有一些約束的

  ()字段必須是volatile類型的!volatile到底是個什麼東西請查看

  ()字段的描述類型(修飾符public/protected/default/private)是與調用者與操作對象字段的關系一致也就是說調用者能夠直接操作對象字段那麼就可以反射進行原子操作但是對於父類的字段子類是不能直接操作的盡管子類可以訪問父類的字段

  ()只能是實例變量不能是類變量也就是說不能加static關鍵字

  ()只能是可修改變量不能使final變量因為final的語義就是不可修改實際上final的語義和volatile是有沖突的這兩個關鍵字不能同時存在

  ()對於AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long類型的字段不能修改其包裝類型(Integer/Long)如果要修改包裝類型就需要使用AtomicReferenceFieldUpdater

  在下面的例子中描述了操作的方法

  [java]

  import ncurrentatomicAtomicIntegerFieldUpdater;

  public class AtomicIntegerFieldUpdaterDemo {

  class DemoData{

  public volatile int value = ;

  volatile int value = ;

  protected volatile int value = ;

  private volatile int value = ;

  }

  AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName) {

  return AtomicIntegerFieldUpdaternewUpdater(DemoDataclass fieldName)

  }

  void doit() {

  DemoData data = new DemoData()

  Systemoutprintln( ==> +getUpdater(valuegetAndSet(data ))

  Systemoutprintln( ==> +getUpdater(valueincrementAndGet(data))

  Systemoutprintln( ==> +getUpdater(valuedecrementAndGet(data))

  Systemoutprintln(true ==> +getUpdater(valuecompareAndSet(data ))

  }

  public static void main(String[] args) {

  AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo()

  demodoit()

  }

  }

  在上面的例子中DemoData的字段value/value對於AtomicIntegerFieldUpdaterDemo類是不可見的因此通過反射是不能直接修改其值的

  AtomicMarkableReference類描述的一個<ObjectBoolean>的對可以原子的修改Object或者Boolean的值這種數據結構在一些緩存或者狀態描述中比較有用這種結構在單個或者同時修改Object/Boolean的時候能夠有效的提高吞吐量

  AtomicStampedReference類維護帶有整數標志的對象引用可以用原子方式對其進行更新對比AtomicMarkableReference類的<ObjectBoolean>AtomicStampedReference維護的是一種類似<Objectint>的數據結構其實就是對對象(引用)的一個並發計數但是與AtomicInteger不同的是此數據結構可以攜帶一個對象引用(Object)並且能夠對此對象和計數同時進行原子操作

  在本文結尾會提到ABA問題而AtomicMarkableReference/AtomicStampedReference在解決ABA問題上很有用

  Atomic類的作用

  使得讓對單一數據的操作實現了原子化

  使用Atomic類構建復雜的無需阻塞的代碼

  訪問對個或個以上的atomic變量(或者對單個atomic變量進行次或次以上的操作)通常認為是需要同步的以達到讓這些操作能被作為一個原子單元

  無鎖定且無等待算法

  基於 CAS (compare and swap)的並發算法稱為 無鎖定算法因為線程不必再等待鎖定(有時稱為互斥或關鍵部分這取決於線程平台的術語)無論 CAS 操作成功還是失敗在任何一種情況中它都在可預知的時間內完成如果 CAS 失敗調用者可以重試 CAS 操作或采取其他適合的操作

  如果每個線程在其他線程任意延遲(或甚至失敗)時都將持續進行操作就可以說該算法是 無等待的與此形成對比的是 無鎖定算法要求僅 某個線程總是執行操作(無等待的另一種定義是保證每個線程在其有限的步驟中正確計算自己的操作而不管其他線程的操作計時交叉或速度這一限制可以是系統中線程數的函數例如如果有 個線程每個線程都執行一次CasCounterincrement() 操作最壞的情況下每個線程將必須重試最多九次才能完成增加

  再過去的 年裡人們已經對無等待且無鎖定算法(也稱為 無阻塞算法)進行了大量研究許多人通用數據結構已經發現了無阻塞算法無阻塞算法被廣泛用於操作系統和 JVM 級別進行諸如線程和進程調度等任務雖然它們的實現比較復雜但相對於基於鎖定的備選算法它們有許多優點可以避免優先級倒置和死鎖等危險競爭比較便宜協調發生在更細的粒度級別允許更高程度的並行機制等等

  常見的

  非阻塞的計數器Counter

  非阻塞堆棧ConcurrentStack

  非阻塞的鏈表ConcurrentLinkedQueue


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27474.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.