溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中原子變量類的原理是什么

發(fā)布時(shí)間:2021-06-15 15:22:45 來源:億速云 閱讀:151 作者:Leah 欄目:編程語言

Java中原子變量類的原理是什么,針對這個(gè)問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。

一、原子變量類簡介

為何需要原子變量類

保證線程安全是 Java 并發(fā)編程必須要解決的重要問題。Java 從原子性、可見性、有序性這三大特性入手,確保多線程的數(shù)據(jù)一致性。

確保線程安全最常見的做法是利用鎖機(jī)制(Lock、sychronized)來對共享數(shù)據(jù)做互斥同步,這樣在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊,那么操作必然是原子性的,線程安全的?;コ馔阶钪饕膯栴}是線程阻塞和喚醒所帶來的性能問題。

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

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

原子變量類的作用

原子變量類 比鎖的粒度更細(xì),更輕量級,并且對于在多處理器系統(tǒng)上實(shí)現(xiàn)高性能的并發(fā)代碼來說是非常關(guān)鍵的。原子變量將發(fā)生競爭的范圍縮小到單個(gè)變量上。

原子變量類相當(dāng)于一種泛化的 volatile 變量,能夠支持原子的、有條件的讀/改/寫操作。

原子類在內(nèi)部使用 CAS 指令(基于硬件的支持)來實(shí)現(xiàn)同步。這些指令通常比鎖更快。

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

  • 基本類型

    • AtomicBoolean - 布爾類型原子類

    • AtomicInteger - 整型原子類

    • AtomicLong - 長整型原子類

  • 引用類型

    • AtomicReference - 引用類型原子類

    • AtomicMarkableReference - 帶有標(biāo)記位的引用類型原子類

    • AtomicStampedReference - 帶有版本號的引用類型原子類

  • 數(shù)組類型

    • AtomicIntegerArray - 整形數(shù)組原子類

    • AtomicLongArray - 長整型數(shù)組原子類

    • AtomicReferenceArray - 引用類型數(shù)組原子類

  • 屬性更新器類型

    • AtomicIntegerFieldUpdater - 整型字段的原子更新器。

    • AtomicLongFieldUpdater - 長整型字段的原子更新器。

    • AtomicReferenceFieldUpdater - 原子更新引用類型里的字段。

這里不對 CAS、volatile、互斥同步做深入探討。如果想了解更多細(xì)節(jié),不妨參考:Java 并發(fā)核心機(jī)制

二、基本類型

這一類型的原子類是針對 Java 基本類型進(jìn)行操作。

  • AtomicBoolean - 布爾類型原子類

  • AtomicInteger - 整型原子類

  • AtomicLong - 長整型原子類

以上類都支持 CAS,此外,AtomicInteger、AtomicLong 還支持算術(shù)運(yùn)算。

提示:

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

由于 AtomicBoolean、AtomicInteger、AtomicLong 實(shí)現(xiàn)方式、使用方式都相近,所以本文僅針對 AtomicInteger 進(jìn)行介紹。

AtomicInteger 用法

public final int get() // 獲取當(dāng)前值
public final int getAndSet(int newValue) // 獲取當(dāng)前值,并設(shè)置新值
public final int getAndIncrement()// 獲取當(dāng)前值,并自增
public final int getAndDecrement() // 獲取當(dāng)前值,并自減
public final int getAndAdd(int delta) // 獲取當(dāng)前值,并加上預(yù)期值
boolean compareAndSet(int expect, int update) // 如果輸入值(update)等于預(yù)期值,將該值設(shè)置為輸入值
public final void lazySet(int newValue) // 最終設(shè)置為 newValue,使用 lazySet 設(shè)置之后可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值。

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 實(shí)現(xiàn)

閱讀 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 的修改在并發(fā)環(huán)境下對所有線程可見。

  • valueOffset - value 屬性的偏移量,通過這個(gè)偏移量可以快速定位到 value 字段,這個(gè)是實(shí)現(xiàn) AtomicInteger 的關(guān)鍵。

  • unsafe - Unsafe 類型的屬性,它為 AtomicInteger 提供了 CAS 操作。

三、引用類型

Java 數(shù)據(jù)類型分為 基本數(shù)據(jù)類型 和 引用數(shù)據(jù)類型 兩大類(不了解 Java 數(shù)據(jù)類型劃分可以參考: Java 基本數(shù)據(jù)類型 )。

上一節(jié)中提到了針對基本數(shù)據(jù)類型的原子類,那么如果想針對引用類型做原子操作怎么辦?Java 也提供了相關(guān)的原子類:

  • AtomicReference - 引用類型原子類

  • AtomicMarkableReference - 帶有標(biāo)記位的引用類型原子類

  • AtomicStampedReference - 帶有版本號的引用類型原子類

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

示例:基于 AtomicReference 實(shí)現(xiàn)一個(gè)簡單的自旋鎖

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} 實(shí)現(xiàn)的簡單自旋鎖
   */
  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} 并發(fā)處理數(shù)據(jù)
   */
  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();
      }
    }
  }
}

原子類的實(shí)現(xiàn)基于 CAS 機(jī)制,而 CAS 存在 ABA 問題(不了解 ABA 問題,可以參考:Java 并發(fā)基礎(chǔ)機(jī)制 - CAS 的問題)。正是為了解決 ABA 問題,才有了 AtomicMarkableReference 和 AtomicStampedReference。

AtomicMarkableReference 使用一個(gè)布爾值作為標(biāo)記,修改時(shí)在 true / false 之間切換。這種策略不能根本上解決 ABA 問題,但是可以降低 ABA 發(fā)生的幾率。常用于緩存或者狀態(tài)描述這樣的場景。

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 使用一個(gè)整型值做為版本號,每次更新前先比較版本號,如果一致,才進(jìn)行修改。通過這種策略,可以根本上解決 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());
      }
    }

  }

}

四、數(shù)組類型

Java 提供了以下針對數(shù)組的原子類:

  • AtomicIntegerArray - 整形數(shù)組原子類

  • AtomicLongArray - 長整型數(shù)組原子類

  • AtomicReferenceArray - 引用類型數(shù)組原子類

已經(jīng)有了針對基本類型和引用類型的原子類,為什么還要提供針對數(shù)組的原子類呢?

數(shù)組類型的原子類為 數(shù)組元素 提供了 volatile 類型的訪問語義,這是普通數(shù)組所不具備的特性——volatile 類型的數(shù)組僅在數(shù)組引用上具有 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, index = " + i + ", value = 3");
        }
      }
    }

  }
}

五、屬性更新器類型

更新器類支持基于反射機(jī)制的更新字段值的原子操作。

  • AtomicIntegerFieldUpdater - 整型字段的原子更新器。

  • AtomicLongFieldUpdater - 長整型字段的原子更新器。

  • AtomicReferenceFieldUpdater - 原子更新引用類型里的字段。

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

因?yàn)閷ο蟮膶傩孕薷念愋驮宇惗际浅橄箢悾悦看问褂枚急仨毷褂渺o態(tài)方法 newUpdater() 創(chuàng)建一個(gè)更新器,并且需要設(shè)置想要更新的類和屬性。

  • 字段必須是 volatile 類型的;

  • 不能作用于靜態(tài)變量(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;
  }

 }

}

關(guān)于Java中原子變量類的原理是什么問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI