Java 高并發(fā)十: JDK8對(duì)并發(fā)的新支持詳解
1. LongAdder
和AtomicLong類似的使用方式,但是性能比AtomicLong更好。
LongAdder與AtomicLong都是使用了原子操作來提高性能。但是LongAdder在AtomicLong的基礎(chǔ)上進(jìn)行了熱點(diǎn)分離,熱點(diǎn)分離類似于有鎖操作中的減小鎖粒度,將一個(gè)鎖分離成若干個(gè)鎖來提高性能。在無鎖中,也可以用類似的方式來增加CAS的成功率,從而提高性能。
LongAdder原理圖:
AtomicLong的實(shí)現(xiàn)方式是內(nèi)部有個(gè)value 變量,當(dāng)多線程并發(fā)自增,自減時(shí),均通過CAS 指令從機(jī)器指令級(jí)別操作保證并發(fā)的原子性。唯一會(huì)制約AtomicLong高效的原因是高并發(fā),高并發(fā)意味著CAS的失敗幾率更高, 重試次數(shù)更多,越多線程重試,CAS失敗幾率又越高,變成惡性循環(huán),AtomicLong效率降低。
而LongAdder將把一個(gè)value拆分成若干cell,把所有cell加起來,就是value。所以對(duì)LongAdder進(jìn)行加減操作,只需要對(duì)不同的cell來操作,不同的線程對(duì)不同的cell進(jìn)行CAS操作,CAS的成功率當(dāng)然高了(試想一下3+2+1=6,一個(gè)線程3+1,另一個(gè)線程2+1,最后是8,LongAdder沒有乘法除法的API)。
可是在并發(fā)數(shù)不是很高的情況,拆分成若干的cell,還需要維護(hù)cell和求和,效率不如AtomicLong的實(shí)現(xiàn)。LongAdder用了巧妙的辦法來解決了這個(gè)問題。
初始情況,LongAdder與AtomicLong是相同的,只有在CAS失敗時(shí),才會(huì)將value拆分成cell,每失敗一次,都會(huì)增加cell的數(shù)量,這樣在低并發(fā)時(shí),同樣高效,在高并發(fā)時(shí),這種“自適應(yīng)”的處理方式,達(dá)到一定cell數(shù)量后,CAS將不會(huì)失敗,效率大大提高。
LongAdder是一種以空間換時(shí)間的策略。
2. CompletableFuture
實(shí)現(xiàn)CompletionStage接口(40余個(gè)方法),大多數(shù)方法多數(shù)應(yīng)用在函數(shù)式編程中。并且支持流式調(diào)用
CompletableFuture是Java 8中對(duì)Future的增強(qiáng)版
簡單實(shí)現(xiàn):
import java.util.concurrent.CompletableFuture; public class AskThread implements Runnable { CompletableFuture<Integer> re = null; public AskThread(CompletableFuture<Integer> re) { this.re = re; } @Override public void run() { int myRe = 0; try { myRe = re.get() * re.get(); } catch (Exception e) { } System.out.println(myRe); } public static void main(String[] args) throws InterruptedException { final CompletableFuture<Integer> future = new CompletableFuture<Integer>(); new Thread(new AskThread(future)).start(); // 模擬長時(shí)間的計(jì)算過程 Thread.sleep(1000); // 告知完成結(jié)果 future.complete(60); } }
Future最令人詬病的就是要等待,要自己去檢查任務(wù)是否完成了,在Future中,任務(wù)完成的時(shí)間是不可控的。而 CompletableFuture的最大改進(jìn)在于,任務(wù)完成的時(shí)間也開放了出來。
future.complete(60);
用來設(shè)置完成時(shí)間。
CompletableFuture的異步執(zhí)行:
public static Integer calc(Integer para) { try { // 模擬一個(gè)長時(shí)間的執(zhí)行 Thread.sleep(1000); } catch (InterruptedException e) { } return para * para; } public static void main(String[] args) throws InterruptedException, ExecutionException { final CompletableFuture<Integer> future = CompletableFuture .supplyAsync(() -> calc(50)); System.out.println(future.get()); } CompletableFuture的流式調(diào)用: public static Integer calc(Integer para) { try { // 模擬一個(gè)長時(shí)間的執(zhí)行 Thread.sleep(1000); } catch (InterruptedException e) { } return para * para; } public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu = CompletableFuture .supplyAsync(() -> calc(50)) .thenApply((i) -> Integer.toString(i)) .thenApply((str) -> "\"" + str + "\"") .thenAccept(System.out::println); fu.get(); }
組合多個(gè)CompletableFuture:
public static Integer calc(Integer para) { return para / 2; } public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu = CompletableFuture .supplyAsync(() -> calc(50)) .thenCompose( (i) -> CompletableFuture.supplyAsync(() -> calc(i))) .thenApply((str) -> "\"" + str + "\"") .thenAccept(System.out::println); fu.get(); }
這幾個(gè)例子更多是側(cè)重Java8的一些新特性,這里就簡單舉下例子來說明特性,就不深究了。
CompletableFuture跟性能上關(guān)系不大,更多的是為了支持函數(shù)式編程,在功能上的增強(qiáng)。當(dāng)然開放了完成時(shí)間的設(shè)置是一大亮點(diǎn)。
3. StampedLock
在上一篇中剛剛提到了鎖分離,而鎖分離的重要的實(shí)現(xiàn)就是ReadWriteLock。而StampedLock則是ReadWriteLock的一個(gè)改進(jìn)。StampedLock與ReadWriteLock的區(qū)別在于,StampedLock認(rèn)為讀不應(yīng)阻塞寫,StampedLock認(rèn)為當(dāng)讀寫互斥的時(shí)候,讀應(yīng)該是重讀,而不是不讓寫線程寫。這樣的設(shè)計(jì)解決了讀多寫少時(shí),使用ReadWriteLock會(huì)產(chǎn)生寫線程饑餓現(xiàn)象。
所以StampedLock是一種偏向于寫線程的改進(jìn)。
StampedLock示例:
import java.util.concurrent.locks.StampedLock; public class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; if (!sl.validate(stamp)) { stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }
上述代碼模擬了寫線程和讀線程, StampedLock根據(jù)stamp來查看是否互斥,寫一次stamp變?cè)黾幽硞€(gè)值
tryOptimisticRead()
就是剛剛所說的讀寫不互斥的情況。
每次讀線程要讀時(shí),會(huì)先判斷
if (!sl.validate(stamp))
validate中會(huì)先查看是否有寫線程在寫,然后再判斷輸入的值和當(dāng)前的 stamp是否相同,即判斷是否讀線程將讀到最新的數(shù)據(jù)。
如果有寫線程在寫,或者 stamp數(shù)值不同,則返回失敗。
如果判斷失敗,當(dāng)然可以重復(fù)的嘗試去讀,在示例代碼中,并沒有讓其重復(fù)嘗試讀,而采用的是將樂觀鎖退化成普通的讀鎖去讀,這種情況就是一種悲觀的讀法。
stamp = sl.readLock();
StampedLock的實(shí)現(xiàn)思想:
CLH自旋鎖:當(dāng)鎖申請(qǐng)失敗時(shí),不會(huì)立即將讀線程掛起,在鎖當(dāng)中會(huì)維護(hù)一個(gè)等待線程隊(duì)列,所有申請(qǐng)鎖,但是沒有成功的線程都記錄在這個(gè)隊(duì)列中。每一個(gè)節(jié)點(diǎn)(一個(gè)節(jié)點(diǎn)代表一個(gè)線程),保存一個(gè)標(biāo)記位(locked),用于判斷當(dāng)前線程是否已經(jīng)釋放鎖。當(dāng)一個(gè)線程試圖獲得鎖時(shí),取得當(dāng)前等待隊(duì)列的尾部節(jié)點(diǎn)作為其前序節(jié)點(diǎn)。并使用類似如下代碼判斷前序節(jié)點(diǎn)是否已經(jīng)成功釋放鎖
while (pred.locked) {
}
這個(gè)循環(huán)就是不斷等前面那個(gè)結(jié)點(diǎn)釋放鎖,這樣的自旋使得當(dāng)前線程不會(huì)被操作系統(tǒng)掛起,從而提高了性能。
當(dāng)然不會(huì)進(jìn)行無休止的自旋,會(huì)在若干次自旋后掛起線程。
相關(guān)文章
RabbitMQ 3.9.7 鏡像模式集群與Springboot 2.5.5 整合
今天我們來聊聊 RabbitMQ 3.9.7 鏡像模式集群與Springboot 2.5.5 整合,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-10-10Java中實(shí)現(xiàn)代碼優(yōu)化的技巧分享
這篇文章主要跟大家談?wù)剝?yōu)化這個(gè)話題,那么我們一起聊聊Java中如何實(shí)現(xiàn)代碼優(yōu)化這個(gè)問題,小編這里有幾個(gè)實(shí)用的小技巧分享給大家,需要的可以參考一下2022-08-08Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)
本文主要介紹了Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-04-04Java class文件格式之特殊字符串_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
特殊字符串出現(xiàn)在class文件中的常量池中,本著循序漸進(jìn)和減少跨度的原則, 首先把class文件中的特殊字符串做一個(gè)詳細(xì)的介紹, 然后再回過頭來繼續(xù)講解常量池,對(duì)java class 文件格式相關(guān)知識(shí)感興趣的的朋友一起學(xué)習(xí)吧2017-06-06Java調(diào)用第三方http接口的常用方式總結(jié)
這篇文章主要介紹了Java調(diào)用第三方http接口的常用方式總結(jié),具有很好的參考價(jià)值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06詳解Java中NullPointerException異常的原因詳解以及解決方法
這篇文章主要介紹了詳解Java中NullPointerException異常的原因詳解以及解決方法。文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08java實(shí)現(xiàn)在性能測(cè)試中進(jìn)行業(yè)務(wù)驗(yàn)證實(shí)例
這篇文章主要為大家介紹了java實(shí)現(xiàn)在性能測(cè)試中進(jìn)行業(yè)務(wù)驗(yàn)證實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07