Java原子變量類原理及實(shí)例解析
這篇文章主要介紹了Java原子變量類原理及實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
一、原子變量類簡(jiǎn)介
為何需要原子變量類
保證線程安全是 Java 并發(fā)編程必須要解決的重要問(wèn)題。Java 從原子性、可見性、有序性這三大特性入手,確保多線程的數(shù)據(jù)一致性。
確保線程安全最常見的做法是利用鎖機(jī)制(Lock、sychronized)來(lái)對(duì)共享數(shù)據(jù)做互斥同步,這樣在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊,那么操作必然是原子性的,線程安全的。互斥同步最主要的問(wèn)題是線程阻塞和喚醒所帶來(lái)的性能問(wèn)題。
volatile 是輕量級(jí)的鎖(自然比普通鎖性能要好),它保證了共享變量在多線程中的可見性,但無(wú)法保證原子性。所以,它只能在一些特定場(chǎng)景下使用。
為了兼顧原子性以及鎖帶來(lái)的性能問(wèn)題,Java 引入了 CAS (主要體現(xiàn)在 Unsafe 類)來(lái)實(shí)現(xiàn)非阻塞同步(也叫樂(lè)觀鎖)。并基于 CAS ,提供了一套原子工具類。
原子變量類的作用
原子變量類 比鎖的粒度更細(xì),更輕量級(jí),并且對(duì)于在多處理器系統(tǒng)上實(shí)現(xiàn)高性能的并發(fā)代碼來(lái)說(shuō)是非常關(guān)鍵的。原子變量將發(fā)生競(jìng)爭(zhēng)的范圍縮小到單個(gè)變量上。
原子變量類相當(dāng)于一種泛化的 volatile 變量,能夠支持原子的、有條件的讀/改/寫操作。
原子類在內(nèi)部使用 CAS 指令(基于硬件的支持)來(lái)實(shí)現(xiàn)同步。這些指令通常比鎖更快。
原子變量類可以分為 4 組:
- 基本類型
- AtomicBoolean - 布爾類型原子類
- AtomicInteger - 整型原子類
- AtomicLong - 長(zhǎng)整型原子類
- 引用類型
- AtomicReference - 引用類型原子類
- AtomicMarkableReference - 帶有標(biāo)記位的引用類型原子類
- AtomicStampedReference - 帶有版本號(hào)的引用類型原子類
- 數(shù)組類型
- AtomicIntegerArray - 整形數(shù)組原子類
- AtomicLongArray - 長(zhǎng)整型數(shù)組原子類
- AtomicReferenceArray - 引用類型數(shù)組原子類
- 屬性更新器類型
- AtomicIntegerFieldUpdater - 整型字段的原子更新器。
- AtomicLongFieldUpdater - 長(zhǎng)整型字段的原子更新器。
- AtomicReferenceFieldUpdater - 原子更新引用類型里的字段。
這里不對(duì) CAS、volatile、互斥同步做深入探討。如果想了解更多細(xì)節(jié),不妨參考:Java 并發(fā)核心機(jī)制
二、基本類型
這一類型的原子類是針對(duì) Java 基本類型進(jìn)行操作。
- AtomicBoolean - 布爾類型原子類
- AtomicInteger - 整型原子類
- AtomicLong - 長(zhǎng)整型原子類
以上類都支持 CAS,此外,AtomicInteger、AtomicLong 還支持算術(shù)運(yùn)算。
提示:
雖然 Java 只提供了 AtomicBoolean 、AtomicInteger、AtomicLong,但是可以模擬其他基本類型的原子變量。要想模擬其他基本類型的原子變量,可以將 short 或 byte 等類型與 int 類型進(jìn)行轉(zhuǎn)換,以及使用 Float.floatToIntBits 、Double.doubleToLongBits 來(lái)轉(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;
說(shuō)明:
- value - value 屬性使用 volatile 修飾,使得對(duì) value 的修改在并發(fā)環(huán)境下對(duì)所有線程可見。
- valueOffset - value 屬性的偏移量,通過(guò)這個(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 問(wèn)題,其它的 CAS 能力與另外兩個(gè)類相近,所以最具代表性。因此,本節(jié)只針對(duì) AtomicStampedReference 進(jìn)行說(shuō)明。
示例:基于 AtomicReference 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的自旋鎖
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)的簡(jiǎ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 問(wèn)題(不了解 ABA 問(wèn)題,可以參考:Java 并發(fā)基礎(chǔ)機(jī)制 - CAS 的問(wèn)題)。正是為了解決 ABA 問(wèn)題,才有了 AtomicMarkableReference 和 AtomicStampedReference。
AtomicMarkableReference 使用一個(gè)布爾值作為標(biāo)記,修改時(shí)在 true / false 之間切換。這種策略不能根本上解決 ABA 問(wèn)題,但是可以降低 ABA 發(fā)生的幾率。常用于緩存或者狀態(tài)描述這樣的場(chǎng)景。
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() + " 修改了對(duì)象!"); System.out.println("新的對(duì)象為:" + amr.getReference()); } } }); } executorService.shutdown(); executorService.awaitTermination(3, TimeUnit.SECONDS); } }
AtomicStampedReference 使用一個(gè)整型值做為版本號(hào),每次更新前先比較版本號(hào),如果一致,才進(jìn)行修改。通過(guò)這種策略,可以根本上解決 ABA 問(wèn)題。
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("初始對(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 - 長(zhǎng)整型數(shù)組原子類
- AtomicReferenceArray - 引用類型數(shù)組原子類
已經(jīng)有了針對(duì)基本類型和引用類型的原子類,為什么還要提供針對(duì)數(shù)組的原子類呢?
數(shù)組類型的原子類為 數(shù)組元素 提供了 volatile 類型的訪問(wèn)語(yǔ)義,這是普通數(shù)組所不具備的特性——volatile 類型的數(shù)組僅在數(shù)組引用上具有 volatile 語(yǔ)義。
示例: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 - 長(zhǎng)整型字段的原子更新器。
- 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 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; } } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java編程實(shí)現(xiàn)軌跡壓縮算法開放窗口實(shí)例代碼
這篇文章主要介紹了Java編程實(shí)現(xiàn)軌跡壓縮算法開放窗口實(shí)例代碼,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-11-11spring整合redis緩存并以注解(@Cacheable、@CachePut、@CacheEvict)形式使用
本篇文章主要介紹了spring整合redis緩存并以注解(@Cacheable、@CachePut、@CacheEvict)形式使用,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04struts1實(shí)現(xiàn)簡(jiǎn)單的登錄功能實(shí)例(附源碼)
本篇文章主要介紹了struts1實(shí)現(xiàn)簡(jiǎn)單的登錄功能實(shí)例(附源碼),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04Java統(tǒng)計(jì)代碼的執(zhí)行時(shí)間的N種方法
在日常開發(fā)中經(jīng)常需要測(cè)試一些代碼的執(zhí)行時(shí)間,但又不想使用向 JMH(Java?Microbenchmark Harness,Java 微基準(zhǔn)測(cè)試套件)這么重的測(cè)試框架,所以本文就匯總了一些 Java 中比較常用的執(zhí)行時(shí)間統(tǒng)計(jì)方法,總共包含以下 6 種,需要的朋友可以參考下2022-08-08Java中l(wèi)ambda表達(dá)式的基本運(yùn)用
大家好,本篇文章主要講的是Java中l(wèi)ambda表達(dá)式的基本運(yùn)用,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01帶你輕松搞定Java面向?qū)ο蟮木幊?-數(shù)組,集合框架
Java是面向?qū)ο蟮母呒?jí)編程語(yǔ)言,類和對(duì)象是 Java程序的構(gòu)成核心。圍繞著Java類和Java對(duì)象,有三大基本特性:封裝是Java 類的編寫規(guī)范、繼承是類與類之間聯(lián)系的一種形式、而多態(tài)為系統(tǒng)組件或模塊之間解耦提供了解決方案2021-06-06