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