????????前一段時(shí)間寫(xiě)了socket和多線程的倆篇文章曹鸠,由于工作繁忙最近一致沒(méi)有跟新隘世,有好多開(kāi)發(fā)愛(ài)好者私信小編掀鹅,讓把多線程和socket這塊進(jìn)行補(bǔ)充完畢丘逸,今天抽時(shí)間將多線程高級(jí)進(jìn)階篇進(jìn)行詳細(xì)的補(bǔ)充一下,這里主要涉及到是callable 馏予、ExecutorService以及線程池的相關(guān)技術(shù)蔓纠,其中會(huì)涉及大量的示例,如有看不明白或者講解不透徹的部分吗蚌,大家可以將示例在本地運(yùn)行進(jìn)行觀察數(shù)據(jù)輸出的情況,可以更進(jìn)一步加深多線程的了解纯出。?
? ? ? 一:? 在進(jìn)行線程池方面的講解之前蚯妇,首先我們來(lái)回顧一下java 多線程的三種實(shí)現(xiàn)方式:
1. 第一種是通過(guò)Thread類實(shí)現(xiàn)多線程
示例如下:
? ??????public class TestThread {
static class Personextends Thread{
@Override
? ? ? ? public void run() {
System.out.println(Thread.currentThread().getName());
? ? ? ? }
}
public static void main(String[] args) {
Person person =new Person();
? ? ? ? Person person1 =new Person();
? ? ? ? Person person2 =new Person();
? ? ? ? person.run();
? ? ? ? person1.run();
? ? ? ? person2.run();
? ? }
}
2. 第二種是通過(guò)實(shí)現(xiàn)Runnable接口實(shí)現(xiàn)多線程
public class TestRunnable {
static class Personimplements Runnable{
@Override
? ? ? ? public void run() {
System.out.println(Thread.currentThread().getName());
? ? ? ? }
}
public static void main(String[] args) {
Person person =new Person();
? ? ? ? Person person1 =new Person();
? ? ? ? Person person2 =new Person();
? ? ? ? person.run();
? ? ? ? person1.run();
? ? ? ? person2.run();
? ? }
3. 第三種種通過(guò)實(shí)現(xiàn)Callable接口實(shí)現(xiàn)多線程
Callable接口需要結(jié)合ExecutorService 和Future使用
? ? ?二:? ?多線程種run()方法與start()調(diào)用線程的區(qū)別:
1.????run()方法是按照順序執(zhí)行的敷燎,在main線程種的被當(dāng)作一種普通的方法調(diào)用,所有多線程啟動(dòng)后調(diào)用run()方法他的線程名換是main線程箩言,線程的執(zhí)行過(guò)程種和普通方法調(diào)用一樣硬贯,都是按照順序執(zhí)行的,嚴(yán)格意義上來(lái)說(shuō)他不是多線程陨收。
2.? start()方式是真正意義上的多線程饭豹,在調(diào)用start()方法啟動(dòng)后,除main線程外务漩,會(huì)有單獨(dú)的線程啟動(dòng)拄衰,每一個(gè)線程的名稱和調(diào)用順序都是不同的。
示例如下:
run()方法和普通方法調(diào)用一樣饵骨,輸出的線程都是main線程翘悉。
start()方法是真正意義上的多線程。
????三:? ? 多線程lamda方式與傳統(tǒng)方式實(shí)現(xiàn)區(qū)別:
看一下是不是使用lamda表達(dá)式的時(shí)候居触,程序更加簡(jiǎn)單了妖混,一行代碼就搞定。
? ??????public class TestThread {
public static void main(String[] args) {
Runnable runnable = (() -> System.out.println(Thread.currentThread().getName()));
? ? ? ? runnable.run();
? ? }
}
前面我們穿針引線給大家做一個(gè)簡(jiǎn)單的回顧轮洋,下面我們開(kāi)始講解正餐制市,在之前的多線程種,一致存在種種問(wèn)題弊予,讓程序員詬病祥楣,諸如無(wú)法預(yù)知線程執(zhí)行接口,執(zhí)行狀態(tài)块促,以及多線程管理的麻煩問(wèn)題荣堰,所有Java的開(kāi)發(fā)團(tuán)隊(duì)也一致在尋求線程的改進(jìn),在后續(xù)函數(shù)式接口注解@FunctionalInterface竭翠、java.util.concurrent框架的加入都是為了解決多線程遺留的相關(guān)問(wèn)題振坚。在實(shí)際應(yīng)用種,我們可能會(huì)經(jīng)常遇到以下問(wèn)題:
1.? ? 一個(gè)線程執(zhí)行完畢后斋扰,如何獲取線程執(zhí)行的結(jié)果渡八?
2.? ? 線程執(zhí)行過(guò)程中,如果獲取線程執(zhí)行狀態(tài)已經(jīng)取消正在執(zhí)行的線程传货?
3.? ? 倆個(gè)線程獨(dú)立同時(shí)執(zhí)行屎鳍,但一個(gè)線程又依賴另一個(gè)線程執(zhí)行的結(jié)果?
4.? ? 如果獲取一個(gè)集合中所有線程的執(zhí)行完后后的結(jié)果问裕?
5.? ? 多個(gè)不同的線程通過(guò)不同的方式計(jì)算一個(gè)數(shù)學(xué)問(wèn)題時(shí)逮壁,如果獲取最先計(jì)算完畢線程的結(jié)果(通常計(jì)算最先完成的算法也是最優(yōu)的)?
6.? ?一個(gè)線程執(zhí)行完畢后粮宛,可以異步回調(diào)進(jìn)行通知其他線程窥淆,不需要通過(guò)阻塞的方式卖宠?
7.? ? ?如和通過(guò)手工設(shè)置異步操作完成一個(gè)Future的執(zhí)行?
針對(duì)上面的問(wèn)題忧饭,在最新的concurrent框架下扛伍,我們可以找到答案。
? ? ? ? ? ? 在詳細(xì)講解之前词裤,我們先看下面的這張思維導(dǎo)圖(此圖非本人制作刺洒,如有冒犯請(qǐng)聯(lián)系博主),這張圖可以說(shuō)是非常適合大家理清concurrent框架的下的五大功能點(diǎn):
atomic(原子變量相關(guān))吼砂、tools(工具類)逆航、locks(鎖相關(guān)的內(nèi)容)、executor(線程池相關(guān))帅刊、coolections相關(guān)纸泡。
在詳細(xì)講解concurrent框架時(shí),先以Callable接口為切入點(diǎn)赖瞒,然后涉及Future分裝返回結(jié)果女揭,以及異步調(diào)用過(guò)程中線程狀態(tài)監(jiān)控,結(jié)果合并栏饮、結(jié)果依賴等相關(guān)的CompletableFuture超級(jí)功能類吧兔,最后衍生到locks和atomic等相關(guān)功能,因涉及的功能非常多袍嬉,內(nèi)容非常廣泛境蔼,所有本博文主要詳細(xì)介紹executor功能塊。其他的在后續(xù)章節(jié)中會(huì)進(jìn)行詳細(xì)的描述伺通。其實(shí)大家別被他這么龐大的類和接口嚇到箍土,我們抽絲剝繭把主要的幾個(gè)核心的類和方法調(diào)用摸透了,其他的就用起來(lái)如魚(yú)得水罐监。在介紹的過(guò)程中吴藻,我會(huì)采用一個(gè)方法一個(gè)例子或者多個(gè)方法一個(gè)例子的方式,讓看的明白弓柱。
Future類
首先我們介紹一下Future類沟堡,F(xiàn)uture類是java1.5版本后新的類,主要方法用于檢查計(jì)算是否完成矢空,等待其完成航罗,并檢索計(jì)算結(jié)果。只有當(dāng)計(jì)算完成時(shí)屁药,才能使用方法get檢索結(jié)果粥血,必要時(shí)阻塞,直到準(zhǔn)備好為止。取消由cancel方法執(zhí)行立莉。還提供了其他方法來(lái)確定任務(wù)是否正常完成或取消绢彤。一旦計(jì)算完成,就不能取消計(jì)算蜓耻。
? ? ? ? 下面我們來(lái)看一下Future類的五個(gè)方法,首先我們將五個(gè)方法詳細(xì)邏輯并介紹一下相關(guān)功能械巡,后續(xù)我們會(huì)對(duì)每一個(gè)方法都有通過(guò)詳細(xì)的例子在運(yùn)行刹淌,讓大家徹底明白他的真實(shí)意圖。
1.????boolean?cancel(boolean?mayInterruptIfRunning)讥耗;
官方文檔:試圖取消當(dāng)前任務(wù)的執(zhí)行有勾。如果任務(wù)已經(jīng)完成、已經(jīng)取消或由于其他原因無(wú)法取消古程,則取消失敗蔼卡,返回fase。并且在調(diào)用cancel時(shí)該任務(wù)還沒(méi)有啟動(dòng)挣磨,則該任務(wù)應(yīng)該永遠(yuǎn)不會(huì)運(yùn)行雇逞。如果任務(wù)已經(jīng)啟動(dòng),那么mayInterruptIfRunning參數(shù)確定執(zhí)行該任務(wù)的線程是否應(yīng)該在試圖停止該任務(wù)時(shí)被中斷茁裙。
2.?boolean?isCancelled()
如果此任務(wù)在正常完成之前被取消塘砸,則返回true,如果沒(méi)有取消則返回true
3.?boolean?isDone()
如果該任務(wù)完成晤锥,返回true掉蔬。完成可能是由于正常終止、異撤或取消——在所有這些情況下女轿,該方法將返回true。
4.?V?get()
獲取異步計(jì)算的結(jié)果壕翩,
5.?V?get(long?timeout,TimeUnit?unit)
獲取異步計(jì)算的結(jié)果蛉迹,并且設(shè)置最長(zhǎng)等待時(shí)間,其中第一個(gè)參數(shù)是最長(zhǎng)等待時(shí)間戈泼,第二個(gè)參數(shù)為時(shí)間單位婿禽。從這里我們可以看到設(shè)置線程等待時(shí)間TimeUnit?類也是Java官方的推薦的類,他比Thread.sleep();類對(duì)時(shí)間概念更加直觀大猛。
上面詳細(xì)介紹了Future類的的五個(gè)方法扭倾,下面我們通過(guò)示例演示這五個(gè)方法的真正用途。
import java.util.concurrent.*;
public class TestThread? {
public static void main(String[] args)throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
? ? ? ? Future future = executorService.submit(()->{
return"thread starting挽绩。膛壹。。";
? ? ? ? });
? ? ? ? System.out.println(future.get());
? ? }
}
get()方法用于獲取異步計(jì)算后的結(jié)果,這個(gè)方法是阻塞的模聋,如果結(jié)果沒(méi)有返回肩民,則線程會(huì)一致阻塞在哪里,下面我們分析一下get()方法的實(shí)現(xiàn)链方,get()的實(shí)現(xiàn)在Future 接口的實(shí)現(xiàn)類FutureTask中持痰。首先在類中有一個(gè)用volatile關(guān)鍵字修飾的線程狀態(tài)state字段。線程在啟動(dòng)后祟蚀,通過(guò)FutureTask類的構(gòu)造函數(shù)將state的初始值設(shè)置為NEW(也就是默認(rèn)是0).
private volatile int state;
private static final int NEW? ? ? ? ? =0;
private static final int COMPLETING? =1;
private static final int NORMAL? ? ? =2;
private static final int EXCEPTIONAL? =3;
private static final int CANCELLED? ? =4;
private static final int INTERRUPTING =5;
private static final int INTERRUPTED? =6;
通過(guò)構(gòu)造函數(shù)對(duì)state進(jìn)行賦值操作工窍,初始默認(rèn)值為0.
public FutureTask(Callable callable) {
if (callable ==null)
throw new NullPointerException();
? ? this.callable = callable;
? ? this.state =NEW;? ? ? // ensure visibility of callable
}
get()方法的具體實(shí)現(xiàn),通過(guò)判斷starte是否是完成狀態(tài)前酿,如果state的值小于1患雏,則調(diào)用awaitDone進(jìn)行等待,其中awaitDone()方法有倆個(gè)值罢维,第一個(gè)參數(shù)是否使用等待時(shí)間淹仑,如果設(shè)置false,線程沒(méi)有執(zhí)行完畢肺孵,則一致阻塞在哪里匀借。第二個(gè)參數(shù)是設(shè)置等待的時(shí)間,與第一個(gè)結(jié)合使用悬槽。
? ??????public V get()throws InterruptedException, ExecutionException {
????????????int s =state;
? ? ????????if (s <=COMPLETING)
????????????????s = awaitDone(false, 0L);
? ? ????????????return report(s);
????????}
然后我們?cè)诳碼waitDone()方法的具體實(shí)現(xiàn)怀吻。我們可以看到,首先通過(guò)一個(gè)無(wú)限循化是判斷線程是否被中斷初婆,如果沒(méi)有被中斷蓬坡,則s的值賦值為2.
然后繼續(xù)與COMPLETING進(jìn)行比較,如果大于COMPLETING磅叛,并且WaitNode 為mull則直接返回state的當(dāng)前值屑咳。
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos :0L;
? ? WaitNode?q =null;
? ? boolean queued =false;
? ? for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
? ? ? ? ? ? throw new InterruptedException();
? ? ? ? }
int s =state;
? ? ? ? if (s >COMPLETING) {
if (q !=null)
q.thread =null;
? ? ? ? ? ? return s;
? ? ? ? }
else if (s ==COMPLETING)// cannot time out yet
? ? ? ? ? ? Thread.yield();
? ? ? ? else if (q ==null)
q =new WaitNode();
? ? ? ? else if (!queued)
queued =UNSAFE.compareAndSwapObject(this, waitersOffset,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? q.next =waiters, q);
? ? ? ? else if (timed) {
nanos = deadline - System.nanoTime();
? ? ? ? ? ? if (nanos <=0L) {
removeWaiter(q);
? ? ? ? ? ? ? ? return state;
? ? ? ? ? ? }
LockSupport.parkNanos(this, nanos);
? ? ? ? }
else
? ? ? ? ? ? LockSupport.park(this);
? ? }
}
接下來(lái)我們看isDone()方法,該方法主要是獲取當(dāng)前線程的運(yùn)行狀態(tài)結(jié)果弊琴,如果該線程運(yùn)行結(jié)束則返回true兆龙,其中線程的結(jié)束有多種方式,包括線程的取消敲董、異匙匣剩或者正常結(jié)束都是線程的運(yùn)行結(jié)束狀態(tài),在這里有好多初學(xué)者認(rèn)為時(shí)只有線程正常運(yùn)行結(jié)束后腋寨,才返回true聪铺,這是不正常的,尤其涉及線程運(yùn)行結(jié)束獲取結(jié)果進(jìn)行判斷時(shí)萄窜,一定要注意铃剔,下面我們通過(guò)多個(gè)例子進(jìn)行演示獲取當(dāng)前線程運(yùn)行結(jié)果狀態(tài)撒桨。
public class TestIsDone {
public static void main(String[] args)throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
? ? ? ? Future future = executorService.submit(()->{
System.out.println("thread starting ....");
? ? ? ? });
? ? ? ? Boolean flag =false;
? ? ? do {
flag = future.isDone();
? ? ? ? ? System.out.println(flag);
? ? ? }while (!flag);
? ? }
}
首先我們通過(guò)Executors類創(chuàng)建個(gè)線程池,Executors后期我們會(huì)講解键兜,他采用的是工廠方法模式凤类。然后將線程提交到線程池,并將結(jié)果返回到Future中普气,在線程中我們只做個(gè)一個(gè)輸出“thread starting谜疤。。现诀【ソ兀”。接下來(lái)我們獲取當(dāng)前線程運(yùn)行的結(jié)果狀態(tài)赶盔,在這里我為了方便顯示不同情況下獲取線程運(yùn)行狀態(tài)結(jié)果,通過(guò)一個(gè)循環(huán)獲取每次isDone()執(zhí)行的結(jié)果榆浓,通過(guò)控制臺(tái)我們可以看到于未,線程在運(yùn)行過(guò)程中,第一次獲取false陡鹃,這是由于異步執(zhí)行過(guò)程中烘浦,第一次循序時(shí),線程沒(méi)有結(jié)束萍鲸,所有獲取的結(jié)果為false闷叉,第二次循環(huán)中,線程已經(jīng)執(zhí)行完畢脊阴,所以獲取的結(jié)果為true握侧。在實(shí)際運(yùn)行中,不同電腦可能循環(huán)的次數(shù)不同嘿期。
在上面我們提到過(guò)品擎,獲取線程結(jié)束狀態(tài),他包含的不單是正常結(jié)束的線程返回true备徐,取消的線程萄传,異常終止的線程也會(huì)返回true,總之一句話蜜猾,就是線程結(jié)束了秀菱,那么他就會(huì)返回true,具體是怎么結(jié)束的蹭睡,這不是他關(guān)注的衍菱,下面我們通過(guò)修改上面的程序,在線程運(yùn)行過(guò)程中進(jìn)行取消棠笑,然后獲取線程的狀態(tài)梦碗。
package concurrent.d;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class TestIsDone_2 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
? ? ? ? Future future = executorService.submit(()->{
for(int i=0;i<100;i++){
try {
TimeUnit.SECONDS.sleep(1);
? ? ? ? ? ? ? ? }catch (InterruptedException e) {
e.printStackTrace();
? ? ? ? ? ? ? ? }
System.out.println(i);
? ? ? ? ? ? }
});
? ? ? ? System.out.println("獲取當(dāng)前線程運(yùn)行結(jié)果狀態(tài) "+future.isDone());
? ? ? ? future.cancel(true);
? ? ? ? System.out.println("線程取消后獲取當(dāng)前線程運(yùn)行結(jié)果狀態(tài) "+ future.isDone());
? ? }
}
上面的程序我提交一個(gè)線程到線程池。為了在線程執(zhí)行過(guò)程中我們能很直觀的看到線程執(zhí)行狀態(tài),我在循環(huán)打印1到100數(shù)據(jù)的時(shí)候洪规,沒(méi)次間隔1秒印屁,然后我們獲取當(dāng)前線程是否處于完成狀態(tài),在第一次執(zhí)行中斩例,我們可以看到通過(guò)isDone方法返回false雄人,表示當(dāng)前線程正常執(zhí)行,接著我們運(yùn)行cancel()方法取消當(dāng)前的線程念赶,然后在通過(guò)isDone()方法獲取當(dāng)前線程的運(yùn)行狀態(tài)础钠,結(jié)果返回為true,通過(guò)上述示例我們可以看到isDone()方法返回當(dāng)前線程運(yùn)行的結(jié)果狀態(tài)叉谜,在返回ture的時(shí)候旗吁,他不但包含了線程的正常結(jié)束情況,也包括線程的取消或者異常情況停局。
最后一個(gè)方法我們來(lái)討論一下isCancelled() 方法很钓,該方法表示在實(shí)際業(yè)務(wù)中用來(lái)判斷一個(gè)線程或者一個(gè)任務(wù)是否在完成前進(jìn)行取消。
講在最后:在接下來(lái)的幾個(gè)章節(jié)中董栽,我會(huì)用簡(jiǎn)單的例子把常用的函數(shù)功能進(jìn)行詳細(xì)解析透徹码倦,隨著進(jìn)一步的深入,我們將實(shí)際業(yè)務(wù)中的示例帶入進(jìn)來(lái)锭碳,讓大家更加明了袁稽。