自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

基礎(chǔ)篇:Java原子組件和同步組件

開(kāi)發(fā) 后端
在使用多線(xiàn)程并發(fā)編程的時(shí),經(jīng)常會(huì)遇到對(duì)共享變量修改操作。此時(shí)我們可以選擇ConcurrentHashMap,ConcurrentLinkedQueue來(lái)進(jìn)行安全地存儲(chǔ)數(shù)據(jù)。

[[375754]]

本文轉(zhuǎn)載自微信公眾號(hào)「潛行前行」,作者cscw 。轉(zhuǎn)載本文請(qǐng)聯(lián)系潛行前行公眾號(hào)。  

 前言

在使用多線(xiàn)程并發(fā)編程的時(shí),經(jīng)常會(huì)遇到對(duì)共享變量修改操作。此時(shí)我們可以選擇ConcurrentHashMap,ConcurrentLinkedQueue來(lái)進(jìn)行安全地存儲(chǔ)數(shù)據(jù)。但如果單單是涉及狀態(tài)的修改,線(xiàn)程執(zhí)行順序問(wèn)題,使用Atomic開(kāi)頭的原子組件或者ReentrantLock、CyclicBarrier之類(lèi)的同步組件,會(huì)是更好的選擇,下面將一一介紹它們的原理和用法

  • 原子組件的實(shí)現(xiàn)原理CAS
  • AtomicBoolean、AtomicIntegerArray等原子組件的用法、
  • 同步組件的實(shí)現(xiàn)原理
  • ReentrantLock、CyclicBarrier等同步組件的用法

原子組件的實(shí)現(xiàn)原理CAS

  • cas的底層實(shí)現(xiàn)可以看下之前寫(xiě)的一篇文章:詳解鎖原理,synchronized、volatile+cas底層實(shí)現(xiàn)[1]

應(yīng)用場(chǎng)景

  • 可用來(lái)實(shí)現(xiàn)變量、狀態(tài)在多線(xiàn)程下的原子性操作
  • 可用于實(shí)現(xiàn)同步鎖(ReentrantLock)

原子組件

  • 原子組件的原子性操作是靠使用cas來(lái)自旋操作volatile變量實(shí)現(xiàn)的
  • volatile的類(lèi)型變量保證變量被修改時(shí),其他線(xiàn)程都能看到最新的值
  • cas則保證value的修改操作是原子性的,不會(huì)被中斷

基本類(lèi)型原子類(lèi)

  1. AtomicBoolean //布爾類(lèi)型 
  2. AtomicInteger //正整型數(shù)類(lèi)型 
  3. AtomicLong   //長(zhǎng)整型類(lèi)型 

使用示例

  1. public static void main(String[] args) throws Exception { 
  2.     AtomicBoolean atomicBoolean = new AtomicBoolean(false); 
  3.     //異步線(xiàn)程修改atomicBoolean 
  4.     CompletableFuture<Void> future = CompletableFuture.runAsync(() ->{ 
  5.         try { 
  6.             Thread.sleep(1000); //保證異步線(xiàn)程是在主線(xiàn)程之后修改atomicBoolean為false 
  7.             atomicBoolean.set(false); 
  8.         }catch (Exception e){ 
  9.             throw new RuntimeException(e); 
  10.         } 
  11.     }); 
  12.     atomicBoolean.set(true); 
  13.     future.join(); 
  14.     System.out.println("boolean value is:"+atomicBoolean.get()); 
  15. ---------------輸出結(jié)果------------------ 
  16. boolean value is:false 

引用類(lèi)原子類(lèi)

  1. AtomicReference 
  2. //加時(shí)間戳版本的引用類(lèi)原子類(lèi) 
  3. AtomicStampedReference 
  4. //相當(dāng)于AtomicStampedReference,AtomicMarkableReference關(guān)心的是 
  5. //變量是否還是原來(lái)變量,中間被修改過(guò)也無(wú)所謂 
  6. AtomicMarkableReference 
  • AtomicReference的源碼如下,它內(nèi)部定義了一個(gè)volatile V value,并借助VarHandle(具體子類(lèi)是FieldInstanceReadWrite)實(shí)現(xiàn)原子操作,MethodHandles會(huì)幫忙計(jì)算value在類(lèi)的偏移位置,最后在VarHandle調(diào)用Unsafe.public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x)方法原子修改對(duì)象的屬性
  1. public class AtomicReference<V> implements java.io.Serializable { 
  2.     private static final long serialVersionUID = -1848883965231344442L; 
  3.     private static final VarHandle VALUE; 
  4.     static { 
  5.         try { 
  6.             MethodHandles.Lookup l = MethodHandles.lookup(); 
  7.             VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class); 
  8.         } catch (ReflectiveOperationException e) { 
  9.             throw new ExceptionInInitializerError(e); 
  10.         } 
  11.     } 
  12.     private volatile V value; 
  13.     .... 

ABA問(wèn)題

  • 線(xiàn)程X準(zhǔn)備將變量的值從A改為B,然而這期間線(xiàn)程Y將變量的值從A改為C,然后再改為A;最后線(xiàn)程X檢測(cè)變量值是A,并置換為B。但實(shí)際上,A已經(jīng)不再是原來(lái)的A了
  • 解決方法,是把變量定為唯一類(lèi)型。值可以加上版本號(hào),或者時(shí)間戳。如加上版本號(hào),線(xiàn)程Y的修改變?yōu)锳1->B2->A3,此時(shí)線(xiàn)程X再更新則可以判斷出A1不等于A3
  • AtomicStampedReference的實(shí)現(xiàn)和AtomicReference差不多,不過(guò)它原子修改的變量是volatile Pair pair;,Pair是其內(nèi)部類(lèi)。AtomicStampedReference可以用來(lái)解決ABA問(wèn)題
  1. public class AtomicStampedReference<V> { 
  2.     private static class Pair<T> { 
  3.         final T reference; 
  4.         final int stamp; 
  5.         private Pair(T reference, int stamp) { 
  6.             this.reference = reference; 
  7.             this.stamp = stamp; 
  8.         } 
  9.         static <T> Pair<T> of(T reference, int stamp) { 
  10.             return new Pair<T>(reference, stamp); 
  11.         } 
  12.     } 
  13.     private volatile Pair<V> pair; 
  • 如果我們不關(guān)心變量在中間過(guò)程是否被修改過(guò),而只是關(guān)心當(dāng)前變量是否還是原先的變量,則可以使用AtomicMarkableReference
  • AtomicStampedReference的使用示例
  1. public class Main { 
  2.     public static void main(String[] args) throws Exception { 
  3.         Test old = new Test("hello"), newTest = new Test("world"); 
  4.         AtomicStampedReference<Test> reference = new AtomicStampedReference<>(old, 1); 
  5.         reference.compareAndSet(old, newTest,1,2); 
  6.         System.out.println("對(duì)象:"+reference.getReference().name+";版本號(hào):"+reference.getStamp()); 
  7.     } 
  8. class Test{ 
  9.     Test(String name){ this.name = name; } 
  10.     public String name
  11. ---------------輸出結(jié)果------------------ 
  12. 對(duì)象:world;版本號(hào):2 

數(shù)組原子類(lèi)

  1. AtomicIntegerArray //整型數(shù)組 
  2.  
  3. AtomicLongArray //長(zhǎng)整型數(shù)組 
  4.  
  5. AtomicReferenceArray //引用類(lèi)型數(shù)組 
  • 數(shù)組原子類(lèi)內(nèi)部會(huì)初始一個(gè)final的數(shù)組,它把整個(gè)數(shù)組當(dāng)做一個(gè)對(duì)象,然后根據(jù)下標(biāo)index計(jì)算法元素偏移量,再調(diào)用UNSAFE.compareAndSetReference進(jìn)行原子操作。數(shù)組并沒(méi)被volatile修飾,為了保證元素類(lèi)型在不同線(xiàn)程的可見(jiàn),獲取元素使用到了UNSAFEpublic native Object getReferenceVolatile(Object o, long offset)方法來(lái)獲取實(shí)時(shí)的元素值
  • 使用示例
  1. //元素默認(rèn)初始化為0 
  2. AtomicIntegerArray array = new AtomicIntegerArray(2); 
  3. // 下標(biāo)為0的元素,期待值是0,更新值是1 
  4. array.compareAndSet(0,0,1); 
  5. System.out.println(array.get(0)); 
  6. ---------------輸出結(jié)果------------------ 

屬性原子類(lèi)

  1. AtomicIntegerFieldUpdater  
  2. AtomicLongFieldUpdater 
  3. AtomicReferenceFieldUpdater 
  • 如果操作對(duì)象是某一類(lèi)型的屬性,可以使用AtomicIntegerFieldUpdater原子更新,不過(guò)類(lèi)的屬性需要定義成volatile修飾的變量,保證該屬性在各個(gè)線(xiàn)程的可見(jiàn)性,否則會(huì)報(bào)錯(cuò)
  • 使用示例
  1. public class Main { 
  2.     public static void main(String[] args) { 
  3.         AtomicReferenceFieldUpdater<Test,String> fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Test.class,String.class,"name"); 
  4.         Test test = new Test("hello world"); 
  5.         fieldUpdater.compareAndSet(test,"hello world","siting"); 
  6.         System.out.println(fieldUpdater.get(test)); 
  7.         System.out.println(test.name); 
  8.     } 
  9. class Test{ 
  10.     Test(String name){ this.name = name; } 
  11.     public volatile String name
  12. ---------------輸出結(jié)果------------------ 
  13. siting 
  14. siting 

累加器

  1. Striped64 
  2. LongAccumulator 
  3. LongAdder 
  4. //accumulatorFunction:運(yùn)算規(guī)則,identity:初始值 
  5. public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity) 
  • LongAccumulator和LongAdder都繼承于Striped64,Striped64的主要思想是和ConcurrentHashMap有點(diǎn)類(lèi)似,分段計(jì)算,單個(gè)變量計(jì)算并發(fā)性能慢時(shí),我們可以把數(shù)學(xué)運(yùn)算分散在多個(gè)變量,而需要計(jì)算總值時(shí),再一一累加起來(lái)
  • LongAdder相當(dāng)于LongAccumulator一個(gè)特例實(shí)現(xiàn)
  • LongAccumulator的示例
  1. public static void main(String[] args) throws Exception { 
  2.     LongAccumulator accumulator = new LongAccumulator(Long::sum, 0); 
  3.     for(int i=0;i<100000;i++){ 
  4.         CompletableFuture.runAsync(() -> accumulator.accumulate(1)); 
  5.     } 
  6.     Thread.sleep(1000); //等待全部CompletableFuture線(xiàn)程執(zhí)行完成,再獲取 
  7.     System.out.println(accumulator.get()); 
  8. ---------------輸出結(jié)果------------------ 
  9. 100000 

同步組件的實(shí)現(xiàn)原理

java的多數(shù)同步組件會(huì)在內(nèi)部維護(hù)一個(gè)狀態(tài)值,和原子組件一樣,修改狀態(tài)值時(shí)一般也是通過(guò)cas來(lái)實(shí)現(xiàn)。而狀態(tài)修改的維護(hù)工作被Doug Lea抽象出AbstractQueuedSynchronizer(AQS)來(lái)實(shí)現(xiàn)

AQS的原理可以看下之前寫(xiě)的一篇文章:詳解鎖原理,synchronized、volatile+cas底層實(shí)現(xiàn)[2]

同步組件

ReentrantLock、ReentrantReadWriteLock

  • ReentrantLock、ReentrantReadWriteLock都是基于AQS(AbstractQueuedSynchronizer)實(shí)現(xiàn)的。因?yàn)樗鼈冇泄芥i和非公平鎖的區(qū)分,因此沒(méi)直接繼承AQS,而是使用內(nèi)部類(lèi)去繼承,公平鎖和非公平鎖各自實(shí)現(xiàn)AQS,ReentrantLock、ReentrantReadWriteLock再借助內(nèi)部類(lèi)來(lái)實(shí)現(xiàn)同步
  • ReentrantLock的使用示例
  1. ReentrantLock lock = new ReentrantLock(); 
  2. if(lock.tryLock()){ 
  3.     //業(yè)務(wù)邏輯 
  4.     lock.unlock(); 
  • ReentrantReadWriteLock的使用示例
  1. public static void main(String[] args) throws Exception { 
  2.     ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 
  3.     if(lock.readLock().tryLock()){ //讀鎖 
  4.         //業(yè)務(wù)邏輯 
  5.         lock.readLock().unlock(); 
  6.     } 
  7.     if(lock.writeLock().tryLock()){ //寫(xiě)鎖 
  8.         //業(yè)務(wù)邏輯 
  9.         lock.writeLock().unlock(); 
  10.     } 

Semaphore實(shí)現(xiàn)原理和使用場(chǎng)景

  • Semaphore和ReentrantLock一樣,也有公平和非公平競(jìng)爭(zhēng)鎖的策略,一樣也是通過(guò)內(nèi)部類(lèi)繼承AQS來(lái)實(shí)現(xiàn)同步
  • 通俗解釋?zhuān)杭僭O(shè)有一口井,最多有三個(gè)人的位置打水。每有一個(gè)人打水,則需要占用一個(gè)位置。當(dāng)三個(gè)位置全部占滿(mǎn)時(shí),第四個(gè)人需要打水,則要等待前三個(gè)人中一個(gè)離開(kāi)打水位,才能繼續(xù)獲取打水的位置
  • 使用示例
  1. public static void main(String[] args) throws Exception { 
  2.     Semaphore semaphore = new Semaphore(2); 
  3.     for (int i = 0; i < 3; i++) 
  4.         CompletableFuture.runAsync(() -> { 
  5.             try { 
  6.                 System.out.println(Thread.currentThread().toString() + " start "); 
  7.                 if(semaphore.tryAcquire(1)){ 
  8.                     Thread.sleep(1000); 
  9.                     semaphore.release(1); 
  10.                     System.out.println(Thread.currentThread().toString() + " 無(wú)阻塞結(jié)束 "); 
  11.                 }else { 
  12.                     System.out.println(Thread.currentThread().toString() + " 被阻塞結(jié)束 "); 
  13.                 } 
  14.             } catch (Exception e) { 
  15.                 throw new RuntimeException(e); 
  16.             } 
  17.         }); 
  18.     //保證CompletableFuture 線(xiàn)程被執(zhí)行,主線(xiàn)程再結(jié)束 
  19.     Thread.sleep(2000); 
  20. ---------------輸出結(jié)果------------------ 
  21. Thread[ForkJoinPool.commonPool-worker-19,5,main] start  
  22. Thread[ForkJoinPool.commonPool-worker-5,5,main] start  
  23. Thread[ForkJoinPool.commonPool-worker-23,5,main] start  
  24. Thread[ForkJoinPool.commonPool-worker-23,5,main] 被阻塞結(jié)束  
  25. Thread[ForkJoinPool.commonPool-worker-5,5,main] 無(wú)阻塞結(jié)束  
  26. Thread[ForkJoinPool.commonPool-worker-19,5,main] 無(wú)阻塞結(jié)束  

可以看出三個(gè)線(xiàn)程,因?yàn)樾盘?hào)量設(shè)定為2,第三個(gè)線(xiàn)程是無(wú)法獲取信息成功的,會(huì)打印阻塞結(jié)束

CountDownLatch實(shí)現(xiàn)原理和使用場(chǎng)景

  • CountDownLatch也是靠AQS實(shí)現(xiàn)的同步操作
  • 通俗解釋?zhuān)和嬗螒驎r(shí),假如主線(xiàn)任務(wù)需要靠完成五個(gè)小任務(wù),主線(xiàn)任務(wù)才能繼續(xù)進(jìn)行時(shí)。此時(shí)可以用CountDownLatch,主線(xiàn)任務(wù)阻塞等待,每完成一小任務(wù),就done一次計(jì)數(shù),直到五個(gè)小任務(wù)全部被執(zhí)行才能觸發(fā)主線(xiàn)
  • 使用示例
  1. public static void main(String[] args) throws Exception { 
  2.     CountDownLatch count = new CountDownLatch(2); 
  3.     for (int i = 0; i < 2; i++) 
  4.         CompletableFuture.runAsync(() -> { 
  5.             try { 
  6.                 Thread.sleep(1000); 
  7.                 System.out.println(" CompletableFuture over "); 
  8.                 count.countDown(); 
  9.             } catch (Exception e) { 
  10.                 throw new RuntimeException(e); 
  11.             } 
  12.         }); 
  13.     //等待CompletableFuture線(xiàn)程的完成 
  14.     count.await(); 
  15.     System.out.println(" main over "); 
  16. ---------------輸出結(jié)果------------------ 
  17.  CompletableFuture over  
  18.  CompletableFuture over  
  19.  main over  

CyclicBarrier實(shí)現(xiàn)原理和使用場(chǎng)景

  • CyclicBarrier則是靠ReentrantLock lock和Condition trip屬性來(lái)實(shí)現(xiàn)同步
  • 通俗解釋?zhuān)篊yclicBarrier需要阻塞全部線(xiàn)程到await狀態(tài),然后全部線(xiàn)程再全部被喚醒執(zhí)行。想象有一個(gè)欄桿攔住五只羊,需要當(dāng)五只羊一起站在欄桿時(shí),欄桿才會(huì)被拉起,此時(shí)所有的羊都可以飛跑出羊圈
  • 使用示例
  1. public static void main(String[] args) throws Exception { 
  2.     CyclicBarrier barrier = new CyclicBarrier(2); 
  3.     CompletableFuture.runAsync(()->{ 
  4.         try { 
  5.             System.out.println("CompletableFuture run start-"+ Clock.systemUTC().millis()); 
  6.             barrier.await(); //需要等待main線(xiàn)程也執(zhí)行到await狀態(tài)才能繼續(xù)執(zhí)行 
  7.             System.out.println("CompletableFuture run over-"+ Clock.systemUTC().millis()); 
  8.         }catch (Exception e){ 
  9.             throw new RuntimeException(e); 
  10.         } 
  11.     }); 
  12.     Thread.sleep(1000); 
  13.     //和CompletableFuture線(xiàn)程相互等待 
  14.     barrier.await(); 
  15.     System.out.println("main run over!"); 
  16. ---------------輸出結(jié)果------------------ 
  17. CompletableFuture run start-1609822588881 
  18. main run over! 
  19. CompletableFuture run over-1609822589880 

StampedLock

  • StampedLock不是借助AQS,而是自己內(nèi)部維護(hù)多個(gè)狀態(tài)值,并配合cas實(shí)現(xiàn)的
  • StampedLock具有三種模式:寫(xiě)模式、讀模式、樂(lè)觀讀模式
  • StampedLock的讀寫(xiě)鎖可以相互轉(zhuǎn)換
  1. //獲取讀鎖,自旋獲取,返回一個(gè)戳值 
  2. public long readLock() 
  3. //嘗試加讀鎖,不成功返回0 
  4. public long tryReadLock() 
  5. //解鎖 
  6. public void unlockRead(long stamp)  
  7. //獲取寫(xiě)鎖,自旋獲取,返回一個(gè)戳值 
  8. public long writeLock() 
  9. //嘗試加寫(xiě)鎖,不成功返回0 
  10. public long tryWriteLock() 
  11. //解鎖 
  12. public void unlockWrite(long stamp) 
  13. //嘗試樂(lè)觀讀讀取一個(gè)時(shí)間戳,并配合validate方法校驗(yàn)時(shí)間戳的有效性 
  14. public long tryOptimisticRead() 
  15. //驗(yàn)證stamp是否有效 
  16. public boolean validate(long stamp) 
  • 使用示例
  1. public static void main(String[] args) throws Exception { 
  2.     StampedLock stampedLock = new StampedLock(); 
  3.     long stamp = stampedLock.tryOptimisticRead(); 
  4.     //判斷版本號(hào)是否生效 
  5.     if (!stampedLock.validate(stamp)) { 
  6.         //獲取讀鎖,會(huì)空轉(zhuǎn) 
  7.         stamp = stampedLock.readLock(); 
  8.         long writeStamp = stampedLock.tryConvertToWriteLock(stamp); 
  9.         if (writeStamp != 0) { //成功轉(zhuǎn)為寫(xiě)鎖 
  10.             //fixme 業(yè)務(wù)操作 
  11.             stampedLock.unlockWrite(writeStamp); 
  12.         } else { 
  13.             stampedLock.unlockRead(stamp); 
  14.             //嘗試獲取寫(xiě)讀 
  15.             stamp = stampedLock.tryWriteLock(); 
  16.             if (stamp != 0) { 
  17.                 //fixme 業(yè)務(wù)操作 
  18.                 stampedLock.unlockWrite(writeStamp); 
  19.             } 
  20.         } 
  21.     } 
  22. }     

參考文章

  • 并發(fā)之Striped64(l累加器)[3]

參考資料

[1]詳解鎖原理,synchronized、volatile+cas底層實(shí)現(xiàn): https://juejin.cn/post/6854573210768900110

[2]詳解鎖原理,synchronized、volatile+cas底層實(shí)現(xiàn): https://juejin.cn/post/6854573210768900110

[3]并發(fā)之Striped64(l累加器): https://www.cnblogs.com/gosaint/p/9129867.html

 

責(zé)任編輯:武曉燕 來(lái)源: 潛行前行
相關(guān)推薦

2019-01-15 14:11:50

Android框架組件化

2015-01-12 13:48:55

Android應(yīng)用組件

2021-01-20 13:50:36

鴻蒙HarmonyOS應(yīng)用開(kāi)發(fā)

2009-07-10 17:03:17

AWT組件Swing組件

2021-02-26 14:35:23

開(kāi)發(fā)技能代碼

2021-02-27 10:58:25

基礎(chǔ)組件React

2020-11-19 10:36:16

Java基礎(chǔ)方法

2021-10-14 15:14:36

鴻蒙HarmonyOS應(yīng)用

2020-09-12 16:22:27

Vue

2022-07-06 08:29:12

antdInput 組件

2023-12-29 08:37:59

2021-09-14 09:34:05

鴻蒙HarmonyOS應(yīng)用

2009-06-11 15:26:05

EJB組件EJB容器

2019-03-13 10:10:26

React組件前端

2022-01-21 11:26:19

Java結(jié)構(gòu)語(yǔ)句if語(yǔ)句

2021-01-12 09:04:12

Django FormForm組件開(kāi)發(fā)

2022-02-25 15:50:05

OpenHarmonToggle組件鴻蒙

2021-07-12 10:36:36

Blazor組件入門(mén)

2009-06-24 14:59:00

圖形bean組件JSF圖形組件

2011-08-01 15:57:58

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)