Java中原子變量類的原理是什么,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。
為鹿邑等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及鹿邑網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、鹿邑網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!
一、原子變量類簡介
為何需要原子變量類
保證線程安全是 Java 并發(fā)編程必須要解決的重要問題。Java 從原子性、可見性、有序性這三大特性入手,確保多線程的數(shù)據(jù)一致性。
確保線程安全最常見的做法是利用鎖機(jī)制(Lock、sychronized)來對(duì)共享數(shù)據(jù)做互斥同步,這樣在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊,那么操作必然是原子性的,線程安全的?;コ馔阶钪饕膯栴}是線程阻塞和喚醒所帶來的性能問題。
volatile 是輕量級(jí)的鎖(自然比普通鎖性能要好),它保證了共享變量在多線程中的可見性,但無法保證原子性。所以,它只能在一些特定場景下使用。
為了兼顧原子性以及鎖帶來的性能問題,Java 引入了 CAS (主要體現(xiàn)在 Unsafe 類)來實(shí)現(xiàn)非阻塞同步(也叫樂觀鎖)。并基于 CAS ,提供了一套原子工具類。
原子變量類的作用
原子變量類 比鎖的粒度更細(xì),更輕量級(jí),并且對(duì)于在多處理器系統(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 - 帶有版本號(hào)的引用類型原子類
數(shù)組類型
AtomicIntegerArray - 整形數(shù)組原子類
AtomicLongArray - 長整型數(shù)組原子類
AtomicReferenceArray - 引用類型數(shù)組原子類
屬性更新器類型
AtomicIntegerFieldUpdater - 整型字段的原子更新器。
AtomicLongFieldUpdater - 長整型字段的原子更新器。
AtomicReferenceFieldUpdater - 原子更新引用類型里的字段。
這里不對(duì) CAS、volatile、互斥同步做深入探討。如果想了解更多細(xì)節(jié),不妨參考:Java 并發(fā)核心機(jī)制
二、基本類型
這一類型的原子類是針對(duì) 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)方式、使用方式都相近,所以本文僅針對(duì) 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 修飾,使得對(duì) value 的修改在并發(fā)環(huán)境下對(duì)所有線程可見。
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é)中提到了針對(duì)基本數(shù)據(jù)類型的原子類,那么如果想針對(duì)引用類型做原子操作怎么辦?Java 也提供了相關(guān)的原子類:
AtomicReference - 引用類型原子類
AtomicMarkableReference - 帶有標(biāo)記位的引用類型原子類
AtomicStampedReference - 帶有版本號(hào)的引用類型原子類
AtomicStampedReference 類在引用類型原子類中,徹底地解決了 ABA 問題,其它的 CAS 能力與另外兩個(gè)類相近,所以最具代表性。因此,本節(jié)只針對(duì) 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 AtomicReferenceatomicReference = 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 AtomicMarkableReferenceamr = 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() + " 修改了對(duì)象!"); System.out.println("新的對(duì)象為:" + amr.getReference()); } } }); } executorService.shutdown(); executorService.awaitTermination(3, TimeUnit.SECONDS); } }
AtomicStampedReference 使用一個(gè)整型值做為版本號(hào),每次更新前先比較版本號(hào),如果一致,才進(jìn)行修改。通過這種策略,可以根本上解決 ABA 問題。
public class AtomicStampedReferenceDemo { private final static String INIT_REF = "pool-1-thread-3"; private final static AtomicStampedReferenceasr = new AtomicStampedReference<>(INIT_REF, 0); public static void main(String[] args) throws InterruptedException { System.out.println("初始對(duì)象為:" + 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() + " 修改了對(duì)象!"); System.out.println("新的對(duì)象為:" + asr.getReference()); } } } }
四、數(shù)組類型
Java 提供了以下針對(duì)數(shù)組的原子類:
AtomicIntegerArray - 整形數(shù)組原子類
AtomicLongArray - 長整型數(shù)組原子類
AtomicReferenceArray - 引用類型數(shù)組原子類
已經(jīng)有了針對(duì)基本類型和引用類型的原子類,為什么還要提供針對(duì)數(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)閷?duì)象的屬性修改類型原子類都是抽象類,所以每次使用都必須使用靜態(tài)方法 newUpdater() 創(chuàng)建一個(gè)更新器,并且需要設(shè)置想要更新的類和屬性。
字段必須是 volatile 類型的;
不能作用于靜態(tài)變量(static);
不能作用于常量(final);
public class AtomicReferenceFieldUpdaterDemo { static User user = new User("begin"); static AtomicReferenceFieldUpdaterupdater = 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)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。