摘要:一無鎖方案并發包中的原子類都是基于無鎖方案實現的,相較于傳統的互斥鎖,無鎖并沒有加鎖解鎖線程切換的消耗,因此無鎖解決方案的性能更好,同時無鎖還能夠保證線程安全。線程首先讀取的值并加,如果此時有另一個線程更新了,則期望值和不相等,更新失敗。
一、無鎖方案
Java 并發包中的原子類都是基于無鎖方案實現的,相較于傳統的互斥鎖,無鎖并沒有加鎖、解鎖、線程切換的消耗,因此無鎖解決方案的性能更好,同時無鎖還能夠保證線程安全。
1. 無鎖方案的實現原理無鎖主要依賴 CAS(Compare And Swap) ,即比較并交換,CAS 是一條 CPU 指令,其本身是能夠保證原子性的。CAS 中有三個參數:
共享變量的內存地址 A
用于比較的值 B
共享變量的新值 C
public class SimpleCAS { private int value; public synchronized int cas(int expectVal, int newVal){ int curVal = value; if (expectVal == curVal){ value = newVal; } return curVal; } }
上面的代碼展示了 CAS 的簡單實現,從內存中讀出當前 value 的值,并且需要判斷,期望值 expectVal == curVal 的時候,才會將 value 更新為新值。
仍然以上面的代碼,來實現一個簡單的,基于 CAS 的線程安全的 value+1 方法。這里的 cas 方法僅用于幫助理解,所以執行結果可能有出入。
public class SimpleCAS { private volatile int value; public void addValue(){ int newVal = value + 1; while (value != cas(value, newVal)){ newVal = value + 1; } } private synchronized int cas(int expectVal, int newVal){ int curVal = value; if (expectVal == curVal){ value = newVal; } return curVal; } }
線程首先讀取 value 的值并加 1,如果此時有另一個線程更新了 value,則期望值和 value 不相等,更新失敗。更新失敗后,循環嘗試,重新讀取 value 的值,直到更新成功退出循環。
2. ABA 問題無鎖的實現方案中需要注意的一個問題便是 ABA 問題。
例如上面的代碼,value 的初始值為 0,線程 t1 取到了 value 的值,并將其更新為 1,然后線程又將 value 更新為 0。
假如這個過程中有另外一個線程 t2,和 t1 同時取初始值為 0 的 value,t2 在 t1 執行完后更新 value,這個時候 value 雖然還是為 0,但已經被 t1 修改過了。
大多數情況下,我們并不需要關心 ABA 問題,例如數值型數據的加減,但是對象類型的數據遇到了 ABA 問題的話,可能前后的屬性已經發生了變化,所以需要解決。
解決的辦法也很簡單,給對象類型的數據加上一個版本號即可,每更新一次,版本號加 1,這樣即使對象數據從 A 變成 B 后 又變成 A,但是版本號是遞增的,就可以分辨出對象還是被修改過的。
二、原子類 1. 原子化基本數據類型有三個實現類:AtomicBoolean、AtomicInteger、AtomicLong
常用的方法如下,以 AtomicInteger 為例,其他的類似:
AtomicInteger i = new AtomicInteger(0); i.getAndSet(int newValue);//獲取當前值并設置新值 i.getAndIncrement();//相當于 i ++ i.incrementAndGet();//相當于 ++ i i.getAndDecrement();//相當于 i -- i.decrementAndGet();//相當于 -- i i.addAndGet(int delta);//相當于 i + delta,并返回添加后的值 i.getAndAdd(int delta);//相當于 i + delta,并返回添加前的值 i.compareAndSet(int expect, int update);//CAS 操作,返回 boolean值,表示是否更新成功 i.getAndUpdate(update -> 10);//通過函數更新值 i.updateAndGet(update -> 10);//類似上面2. 原子化對象引用類型
實現類分別是:AtomicReference、AtomicStampedReference、AtomicMarkableReference,其中后兩個可以實現了解決 ABA 問題的方案。
AtomicReference 常用的方法如下:
//假設有一個叫做 Order 的類 AtomicReferenceorderReference = new AtomicReference<>(); orderReference.getAndSet(Order newValue);//獲取并設置 orderReference.set(Order order);//設置值 Order order1 = orderReference.get();//獲取對象 orderReference.compareAndSet(Order expect, Order update);//比較交換 orderReference.getAndUpdate();//通過函數更新值 orderReference.updateAndGet();
AtomicStampedReference 需要傳入初始值和初始 stamp,其中 stamp 相當于對象的版本號(用來解決 ABA 問題),使用示例如下:
AtomicStampedReferencereference = new AtomicStampedReference<>("roseduan", 0); //嘗試修改stamp的值 boolean b = reference.attemptStamp(reference.getReference(), 10); //獲取值 String str = reference.getReference(); //獲取stamp int stamp = reference.getStamp(); //重新設置值和stamp reference.set("I am not roseduan", 20); //比較交換 boolean b1 = reference.compareAndSet("roseduan", "jack", 20, 0);
AtomicMarkableReference 使用一個 mark 標記(boolean 類型) 代替了 AtomicStampedReference 中的 stamp,用這種更簡單的方式來解決 ABA 問題。使用的方式和上面的類似,只是將方法中的 stamp 變為 boolean 類型的值即可。
3. 原子化數組類型實現類有三個:
AtomicIntegerArray:原子化的整型數組
AtomicLongArray:原子化長整型數組
AtomicReferenceArray:原子化對象引用數組
使用和原子化基本類型都是差不多的,只是需要在方法中加上數組下標即可。
4. 原子化對象屬性更新器也有三個實現類:
AtomicIntegerFieldUpdater:更新對象的整型屬性
AtomicLongFieldUpdater:更新對象的長整型屬性
AtomicReferenceFieldUpdater:更新對象的引用型屬性
這三個類都是利用 Java 的反射機制實現的,并且為了保證原子性,要求被更新的對象的屬性必須是 volatile 類型的。使用示例如下:
@Data @Builder public class User { private volatile int age; private volatile long number; private volatile String name; public static void main(String[] args) { User user = User.builder().age(22).number(15553663L).name("roseduan").build(); //更新age屬性的值 AtomicIntegerFieldUpdaterintegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); integerFieldUpdater.set(user, 25); //更新number屬性的值 AtomicLongFieldUpdater longFieldUpdater = AtomicLongFieldUpdater.newUpdater(User.class, "number"); longFieldUpdater.set(user, 1000101L); //更新對象類型的屬性的值 AtomicReferenceFieldUpdater referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name"); referenceFieldUpdater.set(user, "I am not roseduan"); System.out.println(user.toString()); } }
程序中創建了一個 User 類,有三個屬性 age、number、name 分別對應整型、長整型、引用類型。然后使用對象屬性更新器進行屬性值的更新,更新器的其他方法的使用和前面說到的幾種原子化類型類似。
5. 原子化累加器實現類有四個:
DoubleAdder
DoubleAccumulator
LongAdder
LongAccumulator
這幾個類的功能有限,僅用來執行累加操作,但是速度非??臁O旅娼榻B DoubleAdder 和 DoubleAccumulator 的用法,其余兩個類似。
//DoubleAccumulator使用示例 DoubleAccumulator a = new DoubleAccumulator(Double::sum, 0);//設初始值為0 //累加 a.accumulate(1); a.accumulate(2); a.accumulate(3); a.accumulate(4); System.out.println(a.get());//輸出10 //DoubleAdder使用示例 DoubleAdder adder = new DoubleAdder(); adder.add(1); adder.add(2); adder.add(3); adder.add(4); adder.add(5); System.out.println(adder.intValue());//輸出15
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74612.html
摘要:,,面向切面編程。,切點,切面匹配連接點的點,一般與切點表達式相關,就是切面如何切點。例子中,注解就是切點表達式,匹配對應的連接點,通知,指在切面的某個特定的連接點上執行的動作。,織入,將作用在的過程。因為源碼都是英文寫的。 之前《零基礎帶你看Spring源碼——IOC控制反轉》詳細講了Spring容器的初始化和加載的原理,后面《你真的完全了解Java動態代理嗎?看這篇就夠了》介紹了下...
摘要:相反,它由單體中的適配器和使用一個或多個進程間通信機制的服務組成。因為微服務架構的本質是一組圍繞業務功能組織的松耦合服務。如果你嘗試將此類功能實現為服務,則通常會發現,由于過多的進程間通信而導致性能下降。這是快速展示微服務架構價值的好方法。你很有可能正在處理大型復雜的單體應用程序,每天開發和部署應用程序的經歷都很緩慢而且很痛苦。微服務看起來非常適合你的應用程序,但它也更像是一項遙不可及的必殺...
摘要:英特爾機架規模設計則能實現以機架為單位的軟硬件解耦,為裸金屬即服務提供容量更大的資源池,并可通過開放的和協議如和,高效發掘管理和調配這些資源。 江湖上,一直流傳著得IaaS(基礎設施即服務),得公有云天下的說法。想握緊IaaS這柄云端殺手锏,?大熱的裸金屬即服務和容器即服務,還不了解一下??它們為什么如此受人...
摘要:前言本文描述線程線程狀態及狀態轉換,不會涉及過多理論,主要以代碼示例說明線程狀態如何轉換。被終止線程執行完畢正常結束或執行過程中因未捕獲異常意外終止都會是線程進入被終止狀態。線程執行完畢打印狀態。 前言 本文描述Java線程線程狀態及狀態轉換,不會涉及過多理論,主要以代碼示例說明線程狀態如何轉換。 基礎知識 1. 線程狀態 線程可以有6種狀態: New(新建) Runnable(可運...
摘要:鏈路追蹤鏈路追蹤一詞是在年提出的,當時谷歌發布了一篇論文,介紹了谷歌自研的分布式鏈路追蹤的實現原理,還介紹了他們是怎么低成本實現對應用透明的。感興趣的同學可以去深入了解一下鏈路追蹤,希望本文對你有所幫助。 showImg(https://upload-images.jianshu.io/upload_images/13711841-f54b415cc8d07fdc?imageMogr2...
閱讀 2787·2021-11-17 09:33
閱讀 2169·2021-09-03 10:40
閱讀 522·2019-08-29 18:45
閱讀 2956·2019-08-29 16:21
閱讀 613·2019-08-29 11:11
閱讀 3394·2019-08-26 12:00
閱讀 2947·2019-08-23 18:19
閱讀 1094·2019-08-23 12:18