2耍共、線程的并發(fā)工具類
一乃沙、Fork-Join
體現(xiàn)了“分而治之”設(shè)計(jì)思想:將一個(gè)大問題分割為相同改的小問題,且小問題之間無關(guān)聯(lián)
十大計(jì)算機(jī)經(jīng)典算法:快速排序唉锌、堆排序隅肥、歸并排序、二分查找袄简、線性查找腥放、深度優(yōu)先、廣度優(yōu)先绿语、Dijkstra秃症、動(dòng)態(tài)規(guī)劃、樸素貝葉斯分類
其中快速排序汞舱、歸并排序伍纫、二分查找都屬于分而治之的思想
Fork-Join原理
Fork-Join框架:就是在必要的情況下,將一個(gè)大任務(wù)昂芜,進(jìn)行拆分(fork)成若干個(gè)小任務(wù)(拆到不可再拆時(shí))莹规,再將一個(gè)個(gè)的小任務(wù)運(yùn)算的結(jié)果進(jìn)行join匯總。
Fork-Join的使用
我們要使用 ForkJoin 框架泌神,必須首先創(chuàng)建一個(gè) ForkJoin 任務(wù)良漱。它提供在任務(wù) 中執(zhí)行 fork 和 join 的操作機(jī)制,通常我們不直接繼承 ForkjoinTask 類欢际,只需要直接繼承其子類母市。
public abstract class ForkJoinTask<V>
-
RecursiveAction,用于沒有返回結(jié)果的任務(wù)
public abstract class RecursiveAction extends ForkJoinTask<Void>
-
RecursiveTask损趋,用于有返回值的任務(wù)
public abstract class RecursiveTask<V> extends ForkJoinTask<V>
task 要通過 ForkJoinPool 來執(zhí)行患久,使用 invoke或submit/execute 提交,兩者的區(qū)別是:invoke 是同步執(zhí)行浑槽,調(diào)用之后需要等待任務(wù)完成蒋失,才能執(zhí)行后面的代碼;submit/execute是異步執(zhí)行桐玻。submit和execute區(qū)別:submit有返回值篙挽;execute無返回值。
-
compute()方法:
protected abstract V compute();//實(shí)現(xiàn)compute()方法做自己的事情
在我們自己實(shí)現(xiàn)的 compute 方法里镊靴,首先需要判斷任務(wù)是否足夠小铣卡,如果 足夠小就直接執(zhí)行任務(wù)链韭。如果不足夠小,就必須分割成兩個(gè)子任務(wù)煮落,每個(gè)子任務(wù)在調(diào)用 invokeAll()方法時(shí)敞峭,又會(huì)進(jìn)入 compute()方法,看看當(dāng)前子任務(wù)是否需要繼續(xù)分割成孫任務(wù)州邢,如果不需要繼續(xù)分割儡陨,則執(zhí)行當(dāng)前子任務(wù)并返回結(jié)果。
-
join()方法:
匯總合并量淌,使用 join 方法會(huì)等待子任務(wù)執(zhí)行完并得到其結(jié)果骗村。是個(gè)阻塞方法。
二呀枢、CountDownLatch的作用胚股、應(yīng)用場景和實(shí)戰(zhàn):
閉鎖,CountDownLatch 這個(gè)類能夠使一個(gè)線程等待其他線程完成各自的工作后再執(zhí)行裙秋。例如琅拌,應(yīng)用程序的主線程希望在負(fù)責(zé)啟動(dòng)框架服務(wù)的線程已經(jīng)啟動(dòng)所有的框架服務(wù)之后再執(zhí)行。
CountDownLatch 是通過一個(gè)計(jì)數(shù)器來實(shí)現(xiàn)的摘刑,計(jì)數(shù)器的初始值為初始任務(wù)的數(shù)量进宝。每當(dāng)完成了一個(gè)任務(wù)后,計(jì)數(shù)器的值就會(huì)減1 (CountDownLatch.countDown()方法)枷恕。當(dāng)計(jì)數(shù)器值到達(dá)0時(shí)党晋,它表示所有的已經(jīng)完成了任務(wù),然后在閉鎖上等待 CountDownLatch.await()方法的線程就可以恢復(fù)執(zhí)行任務(wù)徐块。
應(yīng)用場景:
實(shí)現(xiàn)最大的并行性:有時(shí)我們想同時(shí)啟動(dòng)多個(gè)線程未玻,實(shí)現(xiàn)最大程度的并行性。 例如胡控,我們想測試一個(gè)單例類扳剿。如果我們創(chuàng)建一個(gè)初始計(jì)數(shù)為 1 的 CountDownLatch,并讓所有線程都在這個(gè)鎖上等待昼激,那么我們可以很輕松地完成 測試庇绽。我們只需調(diào)用 一次 countDown()方法就可以讓所有的等待線程同時(shí)恢復(fù)執(zhí)行。
三橙困、CAS(Compare And Swap)
實(shí)現(xiàn)原子操作可以使用鎖機(jī)制敛劝。但synchronized關(guān)鍵字是基于阻塞的鎖機(jī)制,也就是說當(dāng)一個(gè)線程擁有鎖的時(shí)候纷宇,訪問同一資源的其它線程需要等待,直到該線程釋放鎖是相對于一些需要復(fù)雜操作蛾方。而一些簡單的應(yīng)用場景像捶,如加減操作上陕,使用同步鎖就顯得不那么靈活了。
基本原理:
利用了現(xiàn)代處理器都支持的CAS指令拓春,循環(huán)這個(gè)指令释簿,直至成功為止
基本思路就是,如果這個(gè)地址上的值和期望的值相等硼莽,則給其賦予新值庶溶,否則不做任何事兒,但是要返回原值是多少懂鸵。循環(huán)CAS就是在一個(gè)循環(huán)里不斷的做cas操作偏螺,直到成功為止
CAS實(shí)現(xiàn)原子操作的三大問題
-
ABA問題
因?yàn)镃AS需要在操作值的時(shí)候,檢查值有沒有發(fā)生變化匆光,如果沒有發(fā)生變化則更新套像,但是如果一個(gè)值原來是A,變成了B终息,又變成了A夺巩,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了周崭。
ABA問題的解決思路就是使用版本戳柳譬。
-
循環(huán)時(shí)間長開銷大
自旋CAS如果長時(shí)間不成功,會(huì)給CPU帶來非常大的執(zhí)行開銷续镇。
-
只能保證一個(gè)共享變量的原子操作
當(dāng)對一個(gè)共享變量執(zhí)行操作時(shí)美澳,我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個(gè)共享變量操作時(shí)磨取,循環(huán)CAS就無法保證操作的原子性人柿,這個(gè)時(shí)候就可以用鎖
Jdk中相關(guān)原子操作類的使用
- 更新基本類型類:AtomicInteger 、AtomicBoolean忙厌、AtomicLong
- 更新數(shù)組類:AtomicIntegerArray凫岖、AtomicLongArray、AtomicReferenceArray
-
更新引用類型:AtomicReference逢净、AtomicStampedReference哥放、AtomicMarkableReference
AtomicReference解決的是只能保證一個(gè)共享變量的原子操作,將多個(gè)變量組成一個(gè)對象進(jìn)行操作爹土;
AtomicStampedReference和AtomicMarkableReference差不多甥雕,都是利用版本戳的形式記錄了每次改變以后的版本號。不同點(diǎn)是胀茵,前者使用了pair的intstamp作為計(jì)數(shù)器使用社露,關(guān)心的是動(dòng)過幾次。而后者的pair使用的是boolean琼娘,關(guān)心的是有沒有被人動(dòng)過峭弟。