1. 程式人生 > 程式設計 >Java原子變數類原理及例項解析

Java原子變數類原理及例項解析

這篇文章主要介紹了Java原子變數類原理及例項解析,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

一、原子變數類簡介

為何需要原子變數類

保證執行緒安全是 Java 併發程式設計必須要解決的重要問題。Java 從原子性、可見性、有序性這三大特性入手,確保多執行緒的資料一致性。

確保執行緒安全最常見的做法是利用鎖機制(Lock、sychronized)來對共享資料做互斥同步,這樣在同一個時刻,只有一個執行緒可以執行某個方法或者某個程式碼塊,那麼操作必然是原子性的,執行緒安全的。互斥同步最主要的問題是執行緒阻塞和喚醒所帶來的效能問題。

volatile 是輕量級的鎖(自然比普通鎖效能要好),它保證了共享變數在多執行緒中的可見性,但無法保證原子性。所以,它只能在一些特定場景下使用。

為了兼顧原子性以及鎖帶來的效能問題,Java 引入了 CAS (主要體現在 Unsafe 類)來實現非阻塞同步(也叫樂觀鎖)。並基於 CAS ,提供了一套原子工具類。

原子變數類的作用

原子變數類 比鎖的粒度更細,更輕量級,並且對於在多處理器系統上實現高效能的併發程式碼來說是非常關鍵的。原子變數將發生競爭的範圍縮小到單個變數上。

原子變數類相當於一種泛化的 volatile 變數,能夠支援原子的、有條件的讀/改/寫操作。

原子類在內部使用 CAS 指令(基於硬體的支援)來實現同步。這些指令通常比鎖更快。

原子變數類可以分為 4 組:

  • 基本型別
    • AtomicBoolean - 布林型別原子類
    • AtomicInteger - 整型原子類
    • AtomicLong - 長整型原子類
  • 引用型別
    • AtomicReference - 引用型別原子類
    • AtomicMarkableReference - 帶有標記位的引用型別原子類
    • AtomicStampedReference - 帶有版本號的引用型別原子類
  • 陣列型別
    • AtomicIntegerArray - 整形陣列原子類
    • AtomicLongArray - 長整型陣列原子類
    • AtomicReferenceArray - 引用型別陣列原子類
  • 屬性更新器型別
    • AtomicIntegerFieldUpdater - 整型欄位的原子更新器。
    • AtomicLongFieldUpdater - 長整型欄位的原子更新器。
    • AtomicReferenceFieldUpdater - 原子更新引用型別裡的欄位。

這裡不對 CAS、volatile、互斥同步做深入探討。如果想了解更多細節,不妨參考:Java 併發核心機制

二、基本型別

這一型別的原子類是針對 Java 基本型別進行操作。

  • AtomicBoolean - 布林型別原子類
  • AtomicInteger - 整型原子類
  • AtomicLong - 長整型原子類

以上類都支援 CAS,此外,AtomicInteger、AtomicLong 還支援算術運算。

提示:

雖然 Java 只提供了 AtomicBoolean 、AtomicInteger、AtomicLong,但是可以模擬其他基本型別的原子變數。要想模擬其他基本型別的原子變數,可以將 short 或 byte 等型別與 int 型別進行轉換,以及使用 Float.floatToIntBits 、Double.doubleToLongBits 來轉換浮點數。

由於 AtomicBoolean、AtomicInteger、AtomicLong 實現方式、使用方式都相近,所以本文僅針對 AtomicInteger 進行介紹。

AtomicInteger 用法

public final int get() // 獲取當前值
public final int getAndSet(int newValue) // 獲取當前值,並設定新值
public final int getAndIncrement()// 獲取當前值,並自增
public final int getAndDecrement() // 獲取當前值,並自減
public final int getAndAdd(int delta) // 獲取當前值,並加上預期值
boolean compareAndSet(int expect,int update) // 如果輸入值(update)等於預期值,將該值設定為輸入值
public final void lazySet(int newValue) // 最終設定為 newValue,使用 lazySet 設定之後可能導致其他執行緒在之後的一小段時間內還是可以讀到舊的值。

AtomicInteger 使用示例:

public class AtomicIntegerDemo {

  public static void main(String[] args) throws InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    AtomicInteger count = new AtomicInteger(0);
    for (int i = 0; i < 1000; i++) {
      executorService.submit((Runnable) () -> {
        System.out.println(Thread.currentThread().getName() + " count=" + count.get());
        count.incrementAndGet();
      });
    }

    executorService.shutdown();
    executorService.awaitTermination(30,TimeUnit.SECONDS);
    System.out.println("Final Count is : " + count.get());
  }
}

AtomicInteger 實現

閱讀 AtomicInteger 原始碼,可以看到如下定義:

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;

說明:

  • value - value 屬性使用 volatile 修飾,使得對 value 的修改在併發環境下對所有執行緒可見。
  • valueOffset - value 屬性的偏移量,通過這個偏移量可以快速定位到 value 欄位,這個是實現 AtomicInteger 的關鍵。
  • unsafe - Unsafe 型別的屬性,它為 AtomicInteger 提供了 CAS 操作。

三、引用型別

Java 資料型別分為 基本資料型別 和 引用資料型別 兩大類(不瞭解 Java 資料型別劃分可以參考: Java 基本資料型別 )。

上一節中提到了針對基本資料型別的原子類,那麼如果想針對引用型別做原子操作怎麼辦?Java 也提供了相關的原子類:

  • AtomicReference - 引用型別原子類
  • AtomicMarkableReference - 帶有標記位的引用型別原子類
  • AtomicStampedReference - 帶有版本號的引用型別原子類

AtomicStampedReference 類在引用型別原子類中,徹底地解決了 ABA 問題,其它的 CAS 能力與另外兩個類相近,所以最具代表性。因此,本節只針對 AtomicStampedReference 進行說明。

示例:基於 AtomicReference 實現一個簡單的自旋鎖

public class AtomicReferenceDemo2 {

  private static int ticket = 10;

  public static void main(String[] args) {
    threadSafeDemo();
  }

  private static void threadSafeDemo() {
    SpinLock lock = new SpinLock();
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 5; i++) {
      executorService.execute(new MyThread(lock));
    }
    executorService.shutdown();
  }

  /**
   * 基於 {@link AtomicReference} 實現的簡單自旋鎖
   */
  static class SpinLock {

    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
      Thread current = Thread.currentThread();
      while (!atomicReference.compareAndSet(null,current)) {}
    }

    public void unlock() {
      Thread current = Thread.currentThread();
      atomicReference.compareAndSet(current,null);
    }

  }

  /**
   * 利用自旋鎖 {@link SpinLock} 併發處理資料
   */
  static class MyThread implements Runnable {

    private SpinLock lock;

    public MyThread(SpinLock lock) {
      this.lock = lock;
    }

    @Override
    public void run() {
      while (ticket > 0) {
        lock.lock();
        if (ticket > 0) {
          System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
          ticket--;
        }
        lock.unlock();
      }
    }
  }
}

原子類的實現基於 CAS 機制,而 CAS 存在 ABA 問題(不瞭解 ABA 問題,可以參考:Java 併發基礎機制 - CAS 的問題)。正是為了解決 ABA 問題,才有了 AtomicMarkableReference 和 AtomicStampedReference。

AtomicMarkableReference 使用一個布林值作為標記,修改時在 true / false 之間切換。這種策略不能根本上解決 ABA 問題,但是可以降低 ABA 發生的機率。常用於快取或者狀態描述這樣的場景。

public class AtomicMarkableReferenceDemo {

  private final static String INIT_TEXT = "abc";

  public static void main(String[] args) throws InterruptedException {

    final AtomicMarkableReference<String> amr = new AtomicMarkableReference<>(INIT_TEXT,false);

    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
      executorService.submit(new Runnable() {
        @Override
        public void run() {
          try {
            Thread.sleep(Math.abs((int) (Math.random() * 100)));
          } catch (InterruptedException e) {
            e.printStackTrace();
          }

          String name = Thread.currentThread().getName();
          if (amr.compareAndSet(INIT_TEXT,name,amr.isMarked(),!amr.isMarked())) {
            System.out.println(Thread.currentThread().getName() + " 修改了物件!");
            System.out.println("新的物件為:" + amr.getReference());
          }
        }
      });
    }

    executorService.shutdown();
    executorService.awaitTermination(3,TimeUnit.SECONDS);
  }

}

AtomicStampedReference 使用一個整型值做為版本號,每次更新前先比較版本號,如果一致,才進行修改。通過這種策略,可以根本上解決 ABA 問題。

public class AtomicStampedReferenceDemo {

  private final static String INIT_REF = "pool-1-thread-3";

  private final static AtomicStampedReference<String> asr = new AtomicStampedReference<>(INIT_REF,0);

  public static void main(String[] args) throws InterruptedException {

    System.out.println("初始物件為:" + asr.getReference());

    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 3; i++) {
      executorService.execute(new MyThread());
    }

    executorService.shutdown();
    executorService.awaitTermination(3,TimeUnit.SECONDS);
  }

  static class MyThread implements Runnable {

    @Override
    public void run() {
      try {
        Thread.sleep(Math.abs((int) (Math.random() * 100)));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      final int stamp = asr.getStamp();
      if (asr.compareAndSet(INIT_REF,Thread.currentThread().getName(),stamp,stamp + 1)) {
        System.out.println(Thread.currentThread().getName() + " 修改了物件!");
        System.out.println("新的物件為:" + asr.getReference());
      }
    }

  }

}

四、陣列型別

Java 提供了以下針對陣列的原子類:

  • AtomicIntegerArray - 整形陣列原子類
  • AtomicLongArray - 長整型陣列原子類
  • AtomicReferenceArray - 引用型別陣列原子類

已經有了針對基本型別和引用型別的原子類,為什麼還要提供針對陣列的原子類呢?

陣列型別的原子類為 陣列元素 提供了 volatile 型別的訪問語義,這是普通陣列所不具備的特性——volatile 型別的陣列僅在陣列引用上具有 volatile 語義。

示例:AtomicIntegerArray 使用示例(AtomicLongArray 、AtomicReferenceArray 使用方式也類似)

public class AtomicIntegerArrayDemo {

  private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);

  public static void main(final String[] arguments) throws InterruptedException {

    System.out.println("Init Values: ");
    for (int i = 0; i < atomicIntegerArray.length(); i++) {
      atomicIntegerArray.set(i,i);
      System.out.print(atomicIntegerArray.get(i) + " ");
    }
    System.out.println();

    Thread t1 = new Thread(new Increment());
    Thread t2 = new Thread(new Compare());
    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println("Final Values: ");
    for (int i = 0; i < atomicIntegerArray.length(); i++) {
      System.out.print(atomicIntegerArray.get(i) + " ");
    }
    System.out.println();
  }

  static class Increment implements Runnable {

    @Override
    public void run() {

      for (int i = 0; i < atomicIntegerArray.length(); i++) {
        int value = atomicIntegerArray.incrementAndGet(i);
        System.out.println(Thread.currentThread().getName() + ",index = " + i + ",value = " + value);
      }
    }

  }

  static class Compare implements Runnable {

    @Override
    public void run() {
      for (int i = 0; i < atomicIntegerArray.length(); i++) {
        boolean swapped = atomicIntegerArray.compareAndSet(i,2,3);
        if (swapped) {
          System.out.println(Thread.currentThread().getName() + " swapped,value = 3");
        }
      }
    }

  }
}

五、屬性更新器型別

更新器類支援基於反射機制的更新欄位值的原子操作。

  • AtomicIntegerFieldUpdater - 整型欄位的原子更新器。
  • AtomicLongFieldUpdater - 長整型欄位的原子更新器。
  • AtomicReferenceFieldUpdater - 原子更新引用型別裡的欄位。

這些類的使用有一定限制:

因為物件的屬性修改型別原子類都是抽象類,所以每次使用都必須使用靜態方法 newUpdater() 建立一個更新器,並且需要設定想要更新的類和屬性。

  • 欄位必須是 volatile 型別的;
  • 不能作用於靜態變數(static);
  • 不能作用於常量(final);
public class AtomicReferenceFieldUpdaterDemo {

 static User user = new User("begin");

 static AtomicReferenceFieldUpdater<User,String> updater =
  AtomicReferenceFieldUpdater.newUpdater(User.class,String.class,"name");

 public static void main(String[] args) {
  ExecutorService executorService = Executors.newFixedThreadPool(3);
  for (int i = 0; i < 5; i++) {
   executorService.execute(new MyThread());
  }
  executorService.shutdown();
 }

 static class MyThread implements Runnable {

  @Override
  public void run() {
   if (updater.compareAndSet(user,"begin","end")) {
    try {
     TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " 已修改 name = " + user.getName());
   } else {
    System.out.println(Thread.currentThread().getName() + " 已被其他執行緒修改");
   }
  }

 }

 static class User {

  volatile String name;

  public User(String name) {
   this.name = name;
  }

  public String getName() {
   return name;
  }

  public User setName(String name) {
   this.name = name;
   return this;
  }

 }

}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。