之前說(shuō)的AQS,其實(shí)AQS的設(shè)計(jì)很難達(dá)到的高度,開(kāi)發(fā)中常用的工具想出來(lái),知道大家有這個(gè)需求,開(kāi)發(fā)人員需求可以通過(guò)這種方式降低代碼量,軟件開(kāi)發(fā)思維很重要,抽象的模板方法,模板方法的經(jīng)典實(shí)現(xiàn)(AbstractOwnableSynchronizer),看一些源碼,實(shí)現(xiàn)原理,還需要了解內(nèi)部的原理,原理無(wú)非就是用到了park,unpark,lock,sync,邏輯其實(shí)不復(fù)雜,仔細(xì)看都可以看懂,關(guān)鍵抽象的思維真的很難理解透,看它的源碼就是要理解,原來(lái)可以這么取抽象,大家中存在大量的重復(fù)的邏輯,這時(shí)候需要考慮能否將重復(fù)代碼進(jìn)行抽象,重復(fù)的代碼邏輯,AQS身上類似的邏輯,是否可以抽象成,一個(gè)模板的方法,設(shè)計(jì)模式的提現(xiàn)。增刪改查在很多框架里面也進(jìn)行了抽象,邏輯是固定的,都可以進(jìn)行抽象的。做成地圖的方式學(xué)習(xí)他的思維很重要。

(一)ForkJoin
- ① 介紹
從JDK1.7開(kāi)始,Java提供Fork/Join框架用于并行執(zhí)行任務(wù),它的思想就是講一個(gè)大任務(wù)分割成若干小任務(wù),最終匯總每個(gè)小任務(wù)的結(jié)果得到這個(gè)大任務(wù)的結(jié)果。將一個(gè)復(fù)雜的計(jì)算,按照設(shè)定的閾值進(jìn)行分解成多個(gè)計(jì)算,然后將各個(gè)計(jì)算結(jié)果進(jìn)行匯總。相應(yīng)的ForkJoin將復(fù)雜的計(jì)算當(dāng)做一個(gè)任務(wù)。而分解的多個(gè)計(jì)算則是當(dāng)做一個(gè)子任務(wù)。

- ② 場(chǎng)景
ForkJoinPool 是 ExecutorService接口的實(shí)現(xiàn),它專為可以遞歸分解成為小塊的工作而設(shè)計(jì),for/join框架將任務(wù)分配給線程池中的工作線程,充分利用多處理器的優(yōu)勢(shì),提高程序性能。
使用fork join 的第一步是編寫執(zhí)行一部分工作的代碼。將代碼包裝在ForkJoinTask子類中,通常是RecursiveTask(可以返回結(jié)果) 或 RecursiveAction。
- ③ 實(shí)現(xiàn)思路
- 每個(gè)worker線程都維護(hù)一個(gè)任務(wù)隊(duì)列,即ForkJoinWorkerThread中的任務(wù)隊(duì)列。
- 任務(wù)隊(duì)列是雙向隊(duì)列,這樣可以同時(shí)實(shí)現(xiàn)LIFO和FIFO(First in, First out.先進(jìn)先出。Last in, First out.后進(jìn)先出)
- 子任務(wù)會(huì)被加入到原先任務(wù)所在worker線程的任務(wù)隊(duì)列。
- Worker線程用LIFO的方法取出任務(wù),后進(jìn)隊(duì)列的任務(wù)先取出來(lái)(子任務(wù)總是后加入隊(duì)列,但是需要先執(zhí)行)
- 當(dāng)任務(wù)隊(duì)列為空,會(huì)隨機(jī)從其他的worker的隊(duì)列中拿走一個(gè)任務(wù)執(zhí)行(工作竊?。簊teal work)
- 如果一個(gè)worker線程遇到了join操作,而這個(gè)時(shí)候正在處理其他任務(wù),會(huì)等到這個(gè)任務(wù)結(jié)束。否則直接返回。
- 如果一個(gè)worker線程竊取任務(wù)失敗,它會(huì)用yield或者sleep之類的方法休息一會(huì),再嘗試(如果所有線程都是空閑狀態(tài),即沒(méi)有任務(wù)運(yùn)行,那么該線程也會(huì)進(jìn)入阻塞狀態(tài)等新任務(wù)的到來(lái))
- ⑤ 適用
適用盡可能少的線程池 - 在大多數(shù)情況下,最好的決定是為了每個(gè)應(yīng)用程序或系統(tǒng)使用一個(gè)線程池,如果不需要特定調(diào)整,請(qǐng)使用默認(rèn)的公共線程池,使用合理的閾值將ForkJoinTask拆分為子任務(wù),避免ForkJoinTask中出現(xiàn)任何阻塞(調(diào)用接口,調(diào)用數(shù)據(jù)庫(kù))。
- ⑥ 源碼
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
// 分而治之的理念
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 默認(rèn)情況下,并行線程數(shù)量等于可用處理器的數(shù)量
// ForkJoinPool與其他類型的ExecutorService的區(qū)別主要在于它使用了工作竊取:
// 池中的所有線程都試圖查找和執(zhí)行提交給池的任務(wù)和/或其他活動(dòng)任務(wù)創(chuàng)建的任務(wù)
// (如果不存在工作,則最終阻塞等待工作)。
ForkJoinPool forkJoinPool = new ForkJoinPool();
RecursiveTask<String> recursiveTask = new RecursiveTask<String>() {
@Override
protected String compute() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(toString() + "" + Thread.currentThread());
ForkJoinTask<String> newTask = this.fork();
newTask.join();
System.out.println("執(zhí)行結(jié)束");
return "";
}
};
ForkJoinTask<String> submit = forkJoinPool.submit(recursiveTask);
ForkJoinTask<String> submitx = forkJoinPool.submit(recursiveTask);
System.out.println(submit.get());
recursiveTask.join();
}
}
PS:工作竊取帶來(lái)的性能提升偏理論,API的復(fù)雜性較高,實(shí)際研發(fā)中可控性來(lái)說(shuō)不如其他API。一般使用最多的就是做數(shù)據(jù)處理。接口和數(shù)據(jù)庫(kù)盡量不要使用,線程如何堵塞了就尷尬了。吐槽下,從JDK1.8以后,JDK的源碼越來(lái)越難度了,變量都是一個(gè)字母。