您好,登錄后才能下訂單哦!
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, index = " + i + ", 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; } } }
關于Java中原子變量類的原理是什么問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。