Java中的原子類詳解
Unsafe
概述
在將原子類之前,先介紹下Unsafe類,因?yàn)樵宇惖牟僮鞫际腔谠擃愖龅?/p>
Unsafe對(duì)象提供了非常底層操作內(nèi)存、線程的方法
該類只提供了一個(gè)私有的無(wú)參構(gòu)造,且Unsafe的定義是一個(gè)的私有成員變量,源碼如下
public final class Unsafe { //私有成員靜態(tài)變量 private static final Unsafe theUnsafe; // 構(gòu)造器 private Unsafe() { }
由上一點(diǎn)可知Unsafe對(duì)象只能通過(guò)反射獲取,不能直接創(chuàng)建,獲取方式如下
public class UnsafeUtil { static Unsafe unsafe; static { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); unsafe = (Unsafe) theUnsafe.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException("獲取 Unsafe 對(duì)象異常"); } } static Unsafe getUnsafe() { return unsafe; } }
Unsafe的cas
Unsafe的cas操作是通過(guò)對(duì)象屬性的偏移量、舊值、新值
要操作的屬性還需要使用volatile修飾,否則會(huì)報(bào)非法參數(shù)異常
示例如下
public class UnsafeCas { public static void main(String[] args) throws NoSuchFieldException { Dog dog = new Dog(); Unsafe unsafe = UnsafeUtil.getUnsafe(); // 通過(guò)反射獲取類中屬性的域?qū)ο?--> 供Unsafe對(duì)象獲取域?qū)ο笃屏渴褂? Field id = Dog.class.getDeclaredField("id"); Field age = Dog.class.getDeclaredField("age"); Field name = Dog.class.getDeclaredField("name"); // 根據(jù)域?qū)ο螳@取域?qū)ο蟮钠屏?--> 供Unsafe cas操作時(shí)使用 long idOffset = unsafe.objectFieldOffset(id); long ageOffset = unsafe.objectFieldOffset(age); long nameOffset = unsafe.objectFieldOffset(name); // Unsafe cas 操作, 參數(shù)為要操作對(duì)象,屬性偏移量,舊值,新值 // 如果要保證并發(fā),在加上while(true)循環(huán)判斷該cas操作是否成功來(lái)控制循環(huán)退出條件即可 unsafe.compareAndSwapInt(dog,idOffset,0,1); unsafe.compareAndSwapInt(dog,ageOffset,0,5); unsafe.compareAndSwapObject(dog,nameOffset,null,"狗狗"); System.out.println(dog); } } class Dog{ volatile long id; volatile int age; volatile String name; @Override public String toString() { return "Dog{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } }
原子類
基本類型包裝類-原子類
- AtomicBoolean(原子Boolean類)
- AtomicInteger(原子整形類)
- AtomicLong(原子長(zhǎng)整型類)
以AtomicInteger為例介紹下該組相關(guān)API
public class AtomicIntegerApi { public static void main(String[] args) { AtomicInteger i = new AtomicInteger(0); // 類似于 i++,獲取并自增(i = 0, 結(jié)果 i = 1, 返回 0) System.out.println(i.getAndIncrement()); System.out.println(i.get()); // 類似于 ++i,自增并獲取(i = 1, 結(jié)果 i = 2, 返回 2) System.out.println(i.incrementAndGet()); System.out.println(i.get()); // 類似于 --i,自減并獲?。╥ = 2, 結(jié)果 i = 1, 返回 1) System.out.println(i.decrementAndGet()); System.out.println(i.get()); // 類似于 i--,獲取并自減(i = 1, 結(jié)果 i = 0, 返回 1) System.out.println(i.getAndDecrement()); System.out.println(i.get()); /* * 上述加減操作單元都是1,如果想自定義操作單元,可以用下面方法 * */ // 獲取并加值(i = 0, 結(jié)果 i = 5, 返回 0) System.out.println(i.getAndAdd(5)); System.out.println(i.get()); // 加值并獲?。╥ = 5, 結(jié)果 i = 0, 返回 0) System.out.println(i.addAndGet(-5)); System.out.println(i.get()); /* * 上述均為加減操作,若想進(jìn)行更新操作可以使用如下方法, * 入?yún)橐辉筒僮骱瘮?shù)式接口: IntUnaryOperator updateFunction * */ // 獲取并更新(i = 0, p 為 i 的當(dāng)前值, 結(jié)果 i = -2, 返回 0) // 其中函數(shù)中的操作能保證原子,但函數(shù)需要無(wú)副作用 System.out.println(i.getAndUpdate(p -> p - 2)); System.out.println(i.get()); // 更新并獲?。╥ = -2, p 為 i 的當(dāng)前值, 結(jié)果 i = 0, 返回 0) // 其中函數(shù)中的操作能保證原子,但函數(shù)需要無(wú)副作用 System.out.println(i.updateAndGet(p -> p + 2)); System.out.println(i.get()); /* * 若想進(jìn)行兩個(gè)參數(shù)測(cè)操作,可以使用如下方法 * 入?yún)椋簠?shù)一;要操作的第二個(gè)整除 ,參數(shù)二:一元整型操作函數(shù)式接口: IntUnaryOperator updateFunction * */ // 獲取并計(jì)算(i = 0, p 為 i 的當(dāng)前值, x 為參數(shù)1, 結(jié)果 i = 10, 返回 0) // 其中函數(shù)中的操作能保證原子,但函數(shù)需要無(wú)副作用 // getAndUpdate 如果在 lambda 中引用了外部的局部變量,要保證該局部變量是 final 的 // getAndAccumulate 可以通過(guò) 參數(shù)1 來(lái)引用外部的局部變量,但因?yàn)槠洳辉?lambda 中因此不必是 final System.out.println(i.getAndAccumulate(10, (p, x) -> p + x)); System.out.println(i.get()); // 計(jì)算并獲取(i = 10, p 為 i 的當(dāng)前值, x 為參數(shù)1, 結(jié)果 i = 0, 返回 0) // 其中函數(shù)中的操作能保證原子,但函數(shù)需要無(wú)副作用 System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x)); System.out.println(i.get()); } }
引用類-原子類
- AtomicReference(基礎(chǔ)原子引用類)
- AtomicMarkableReference(可以判斷共享變量是否修改過(guò)的原子引用類)
- AtomicStampedReference(帶戳的原子引用類)
AtomicReference類的使用及問(wèn)題
public class AtomicReferenceUser { static AtomicReference<String> ref = new AtomicReference<>("A"); public static void main(String[] args){ System.out.println("main start..."); // 獲取值 A String prev = ref.get(); // 嘗試改為 C System.out.println("change A->C {}" + ref.compareAndSet(prev, "C")); } }
使用基本原子引用類,會(huì)引發(fā)ABA問(wèn)題:也就是如下場(chǎng)景,如果一個(gè)線程t1在執(zhí)行開始時(shí)獲取到共享變量為A,t1線程執(zhí)行,這是t2線程將共享變量由A->B并執(zhí)行結(jié)束,又有一個(gè)線程t3將B->A并執(zhí)行結(jié)束,這是線程t1要將共享變量由A變?yōu)镃,這時(shí)是會(huì)成功的。代碼如下
public class ABAThreadSafeQuestion { static AtomicReference<String> ref = new AtomicReference<>("A"); public static void main(String[] args) throws InterruptedException { System.out.println("main start..."); // 獲取值 A // 這個(gè)共享變量被它線程修改過(guò)? String prev = ref.get(); other(); TimeUnit.SECONDS.sleep(1); // 嘗試改為 C System.out.println("change A->C {}" + ref.compareAndSet(prev, "C")); } private static void other() { new Thread(() -> { System.out.println("change A->B {}" + ref.compareAndSet(ref.get(), "B")); }, "t1").start(); sleep(0.5); new Thread(() -> { System.out.println("change B->A {}" + ref.compareAndSet(ref.get(), "A")); }, "t2").start(); } }
AtomicMarkableReference類的使用及問(wèn)題 使用AtomicReference原子類操作是會(huì)引發(fā)ABA問(wèn)題,如果我只關(guān)心共享變量是否被修改過(guò),則可以使用AtomicMarkableReference原子類來(lái)解決ABA問(wèn)題,其他線程修改時(shí),原子變量的第二個(gè)參數(shù)
public class AtomicMarkableReferenceSloveABA { static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A",Boolean.TRUE); public static void main(String[] args) throws InterruptedException { System.out.println("main start..."); // 獲取值 A String prev = ref.getReference(); other(); TimeUnit.SECONDS.sleep(1); // 嘗試改為 C System.out.println("change A->C {}" + ref.compareAndSet(prev, "C",Boolean.TRUE,Boolean.FALSE)); } private static void other() { new Thread(() -> { System.out.println("change A->B {}" + ref.compareAndSet(ref.getReference(),"B",Boolean.TRUE,Boolean.FALSE)); }, "t1").start(); sleep(0.5); new Thread(() -> { System.out.println("change B->A {}" + ref.compareAndSet(ref.getReference(), "A",Boolean.TRUE,Boolean.FALSE)); }, "t2").start(); } }
AtomicStampedReference原子類的使用及問(wèn)題
AtomicMarkableReference原子類雖然可以解決ABA問(wèn)題,但是如果需要知道具體修改過(guò)幾次,那么 AtomicMarkableReference原子類就不能滿足需求了,若要解決這個(gè)問(wèn)題,則就要使用AtomicStampedReference原子類
public class ThreadSafeImplByAtomicStampedReference{ static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0); public static void main(String[] args) throws InterruptedException { System.out.println("main start..."); // 獲取值A(chǔ) String prev = ref.getReference(); // 獲取版本號(hào) int stamp = ref.getStamp(); System.out.println("版本 {}" + stamp); // 如果中間有其它線程干擾,發(fā)生了 ABA 現(xiàn)象 other(); sleep(1); // 嘗試改為 C System.out.println("change A->C {}" + ref.compareAndSet(prev, "C", stamp, stamp + 1)); } private static void other() { new Thread(() -> { System.out.println("change A->B {}" + ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1)); System.out.println("更新版本為 {}" + ref.getStamp()); }, "t1").start(); sleep(0.5); new Thread(() -> { System.out.println("change B->A {}" + ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1)); System.out.println("更新版本為 {}" + ref.getStamp()); }, "t2").start(); } }
原子數(shù)組類
- AtomicIntegerArray(整型原子數(shù)組類)
- AtomicLongArray(長(zhǎng)整型原子數(shù)組類)
- AtomicReferenceArray(引用對(duì)象原子數(shù)組類)
public class AtomicArray { public static void main(String[] args) { // 不安全數(shù)組 test( ()->new int[10], (array)->array.length, (array, index) -> array[index]++, array-> System.out.println(Arrays.toString(array)) ); // 原子數(shù)組 test( ()-> new AtomicIntegerArray(10), (array) -> array.length(), (array, index) -> array.getAndIncrement(index), array -> System.out.println(array) ); } /** 參數(shù)1,提供數(shù)組、可以是線程不安全數(shù)組或線程安全數(shù)組 參數(shù)2,獲取數(shù)組長(zhǎng)度的方法 參數(shù)3,自增方法,回傳 array, index 參數(shù)4,打印數(shù)組的方法 */ private static <T> void test( Supplier<T> arraySupplier, Function<T, Integer> lengthFun, BiConsumer<T, Integer> putConsumer, Consumer<T> printConsumer ) { List<Thread> ts = new ArrayList<>(); T array = arraySupplier.get(); int length = lengthFun.apply(array); for (int i = 0; i < length; i++) { // 每個(gè)線程對(duì)數(shù)組作 10000 次操作 ts.add(new Thread(() -> { for (int j = 0; j < 10000; j++) { putConsumer.accept(array, j%length); } })); } // 啟動(dòng)所有線程 ts.forEach(t -> t.start()); // 等所有線程結(jié)束 ts.forEach(t -> { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); printConsumer.accept(array); } }
字段更新器原子類
- AtomicReferenceFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
使用字段更新器,可以對(duì)對(duì)象的某個(gè)屬性(域 Field)進(jìn)行原子操作,只能配合 volatile 修飾的字段使用,否則會(huì)出現(xiàn)異常:java.lang.IllegalArgumentException: Must be volatile type
public class AtomicFieldUpdaterTest { private volatile int field; public static void main(String[] args) { AtomicIntegerFieldUpdater fieldUpdater =AtomicIntegerFieldUpdater.newUpdater(AtomicFieldUpdaterTest.class, "field"); AtomicFieldUpdaterTest test5 = new AtomicFieldUpdaterTest(); fieldUpdater.compareAndSet(test5, 0, 10); // 修改成功 field = 10 System.out.println(test5.field); // 修改失敗 field = 10 fieldUpdater.compareAndSet(test5, 5, 30); System.out.println(test5.field); } }
原子累加器
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
雖然基本類型包裝類自帶原子累加操作方法,但是為了提高性能,JDK8中又提供了原子累加器,下面是效率對(duì)比測(cè)試類
public class AtomicLongAdderTest { public static void main(String[] args) { for (int i = 0; i < 5; i++) { demo(() -> new LongAdder(), adder -> adder.increment()); } for (int i = 0; i < 5; i++) { demo(() -> new AtomicLong(), adder -> adder.getAndIncrement()); } } private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) { T adder = adderSupplier.get(); long start = System.nanoTime(); List<Thread> ts = new ArrayList<>(); // 4 個(gè)線程,每人累加 50 萬(wàn) for (int i = 0; i < 40; i++) { ts.add(new Thread(() -> { for (int j = 0; j < 500000; j++) { action.accept(adder); } })); } ts.forEach(t -> t.start()); ts.forEach(t -> { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); long end = System.nanoTime(); System.out.println(adder + " cost:" + (end - start)/1000000); } }
性能提升的原因很簡(jiǎn)單,就是在有競(jìng)爭(zhēng)時(shí),設(shè)置多個(gè)累加單元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后將結(jié)果匯總。
這樣它們?cè)诶奂訒r(shí)操作的不同的 Cell 變量,因此減少了 CAS 重試失敗,從而提高性能。
到此這篇關(guān)于Java中的原子類詳解的文章就介紹到這了,更多相關(guān)Java原子類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot項(xiàng)目關(guān)閉swagger如何防止漏洞掃描
這篇文章主要介紹了springboot項(xiàng)目關(guān)閉swagger如何防止漏洞掃描,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-05-05Java探索之Thread+IO文件的加密解密代碼實(shí)例
這篇文章主要介紹了Java探索之Thread+IO文件的加密解密代碼實(shí)例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10mybatis-plus的自動(dòng)填充時(shí)間的問(wèn)題(添加到數(shù)據(jù)庫(kù)的時(shí)間比當(dāng)前時(shí)間多4個(gè)小時(shí))
這篇文章主要介紹了mybatis-plus的自動(dòng)填充時(shí)間的問(wèn)題(添加到數(shù)據(jù)庫(kù)的時(shí)間比當(dāng)前時(shí)間多4個(gè)小時(shí)),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09如何解決Eclipse找不到或無(wú)法加載主類問(wèn)題
這篇文章主要介紹了如何解決Eclipse找不到或無(wú)法加載主類問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06java實(shí)現(xiàn)上傳圖片并壓縮圖片大小功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)上傳圖片并壓縮圖片大小功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Java中@ConditionalOnProperty注解使用
在Spring?Boot中,@ConditionalOnProperty注解是一種方便的工具,用于根據(jù)應(yīng)用程序配置文件中的屬性值來(lái)控制Bean的創(chuàng)建和加載,本文就來(lái)介紹一下Java中@ConditionalOnProperty注解使用,感興趣的可以了解一下2023-11-11springboot集成opencv實(shí)現(xiàn)人臉識(shí)別功能的詳細(xì)步驟
大家都知道OpenCV是一個(gè)基于BSD許可(開源)發(fā)行的跨平臺(tái)計(jì)算機(jī)視覺(jué)和機(jī)器學(xué)習(xí)軟件庫(kù),可以運(yùn)行在Linux、Windows、Android和Mac OS操作系統(tǒng)上今天通過(guò)本文給大家分享springboot集成opencv實(shí)現(xiàn)人臉識(shí)別,感興趣的朋友一起看看吧2021-06-06