????????前一段時間寫了socket和多線程的倆篇文章,由于工作繁忙最近一致沒有跟新,有好多開發(fā)愛好者私信小編,讓把多線程和socket這塊進行補充完畢,今天抽時間將多線程高級進階篇進行詳細的補充一下,這里主要涉及到是callable 、ExecutorService以及線程池的相關(guān)技術(shù),其中會涉及大量的示例,如有看不明白或者講解不透徹的部分,大家可以將示例在本地運行進行觀察數(shù)據(jù)輸出的情況,可以更進一步加深多線程的了解。?
? ? ? 一:? 在進行線程池方面的講解之前,首先我們來回顧一下java 多線程的三種實現(xiàn)方式:
1. 第一種是通過Thread類實現(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. 第二種是通過實現(xiàn)Runnable接口實現(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. 第三種種通過實現(xiàn)Callable接口實現(xiàn)多線程
Callable接口需要結(jié)合ExecutorService 和Future使用
? ? ?二:? ?多線程種run()方法與start()調(diào)用線程的區(qū)別:
1.????run()方法是按照順序執(zhí)行的,在main線程種的被當(dāng)作一種普通的方法調(diào)用,所有多線程啟動后調(diào)用run()方法他的線程名換是main線程,線程的執(zhí)行過程種和普通方法調(diào)用一樣,都是按照順序執(zhí)行的,嚴(yán)格意義上來說他不是多線程。
2.? start()方式是真正意義上的多線程,在調(diào)用start()方法啟動后,除main線程外,會有單獨的線程啟動,每一個線程的名稱和調(diào)用順序都是不同的。
示例如下:
run()方法和普通方法調(diào)用一樣,輸出的線程都是main線程。

start()方法是真正意義上的多線程。

????三:? ? 多線程lamda方式與傳統(tǒng)方式實現(xiàn)區(qū)別:
看一下是不是使用lamda表達式的時候,程序更加簡單了,一行代碼就搞定。
? ??????public class TestThread {
public static void main(String[] args) {
Runnable runnable = (() -> System.out.println(Thread.currentThread().getName()));
? ? ? ? runnable.run();
? ? }
}
前面我們穿針引線給大家做一個簡單的回顧,下面我們開始講解正餐,在之前的多線程種,一致存在種種問題,讓程序員詬病,諸如無法預(yù)知線程執(zhí)行接口,執(zhí)行狀態(tài),以及多線程管理的麻煩問題,所有Java的開發(fā)團隊也一致在尋求線程的改進,在后續(xù)函數(shù)式接口注解@FunctionalInterface、java.util.concurrent框架的加入都是為了解決多線程遺留的相關(guān)問題。在實際應(yīng)用種,我們可能會經(jīng)常遇到以下問題:
1.? ? 一個線程執(zhí)行完畢后,如何獲取線程執(zhí)行的結(jié)果?
2.? ? 線程執(zhí)行過程中,如果獲取線程執(zhí)行狀態(tài)已經(jīng)取消正在執(zhí)行的線程?
3.? ? 倆個線程獨立同時執(zhí)行,但一個線程又依賴另一個線程執(zhí)行的結(jié)果?
4.? ? 如果獲取一個集合中所有線程的執(zhí)行完后后的結(jié)果?
5.? ? 多個不同的線程通過不同的方式計算一個數(shù)學(xué)問題時,如果獲取最先計算完畢線程的結(jié)果(通常計算最先完成的算法也是最優(yōu)的)?
6.? ?一個線程執(zhí)行完畢后,可以異步回調(diào)進行通知其他線程,不需要通過阻塞的方式?
7.? ? ?如和通過手工設(shè)置異步操作完成一個Future的執(zhí)行?
針對上面的問題,在最新的concurrent框架下,我們可以找到答案。
? ? ? ? ? ? 在詳細講解之前,我們先看下面的這張思維導(dǎo)圖(此圖非本人制作,如有冒犯請聯(lián)系博主),這張圖可以說是非常適合大家理清concurrent框架的下的五大功能點:
atomic(原子變量相關(guān))、tools(工具類)、locks(鎖相關(guān)的內(nèi)容)、executor(線程池相關(guān))、coolections相關(guān)。

在詳細講解concurrent框架時,先以Callable接口為切入點,然后涉及Future分裝返回結(jié)果,以及異步調(diào)用過程中線程狀態(tài)監(jiān)控,結(jié)果合并、結(jié)果依賴等相關(guān)的CompletableFuture超級功能類,最后衍生到locks和atomic等相關(guān)功能,因涉及的功能非常多,內(nèi)容非常廣泛,所有本博文主要詳細介紹executor功能塊。其他的在后續(xù)章節(jié)中會進行詳細的描述。其實大家別被他這么龐大的類和接口嚇到,我們抽絲剝繭把主要的幾個核心的類和方法調(diào)用摸透了,其他的就用起來如魚得水。在介紹的過程中,我會采用一個方法一個例子或者多個方法一個例子的方式,讓看的明白。
Future類
首先我們介紹一下Future類,F(xiàn)uture類是java1.5版本后新的類,主要方法用于檢查計算是否完成,等待其完成,并檢索計算結(jié)果。只有當(dāng)計算完成時,才能使用方法get檢索結(jié)果,必要時阻塞,直到準(zhǔn)備好為止。取消由cancel方法執(zhí)行。還提供了其他方法來確定任務(wù)是否正常完成或取消。一旦計算完成,就不能取消計算。
? ? ? ? 下面我們來看一下Future類的五個方法,首先我們將五個方法詳細邏輯并介紹一下相關(guān)功能,后續(xù)我們會對每一個方法都有通過詳細的例子在運行,讓大家徹底明白他的真實意圖。
1.????boolean?cancel(boolean?mayInterruptIfRunning);
官方文檔:試圖取消當(dāng)前任務(wù)的執(zhí)行。如果任務(wù)已經(jīng)完成、已經(jīng)取消或由于其他原因無法取消,則取消失敗,返回fase。并且在調(diào)用cancel時該任務(wù)還沒有啟動,則該任務(wù)應(yīng)該永遠不會運行。如果任務(wù)已經(jīng)啟動,那么mayInterruptIfRunning參數(shù)確定執(zhí)行該任務(wù)的線程是否應(yīng)該在試圖停止該任務(wù)時被中斷。
2.?boolean?isCancelled()
如果此任務(wù)在正常完成之前被取消,則返回true,如果沒有取消則返回true
3.?boolean?isDone()
如果該任務(wù)完成,返回true。完成可能是由于正常終止、異?;蛉∠谒羞@些情況下,該方法將返回true。
4.?V?get()
獲取異步計算的結(jié)果,
5.?V?get(long?timeout,TimeUnit?unit)
獲取異步計算的結(jié)果,并且設(shè)置最長等待時間,其中第一個參數(shù)是最長等待時間,第二個參數(shù)為時間單位。從這里我們可以看到設(shè)置線程等待時間TimeUnit?類也是Java官方的推薦的類,他比Thread.sleep();類對時間概念更加直觀。
上面詳細介紹了Future類的的五個方法,下面我們通過示例演示這五個方法的真正用途。
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()方法用于獲取異步計算后的結(jié)果,這個方法是阻塞的,如果結(jié)果沒有返回,則線程會一致阻塞在哪里,下面我們分析一下get()方法的實現(xiàn),get()的實現(xiàn)在Future 接口的實現(xiàn)類FutureTask中。首先在類中有一個用volatile關(guān)鍵字修飾的線程狀態(tài)state字段。線程在啟動后,通過FutureTask類的構(gòu)造函數(shù)將state的初始值設(shè)置為NEW(也就是默認是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;
通過構(gòu)造函數(shù)對state進行賦值操作,初始默認值為0.
public FutureTask(Callable callable) {
if (callable ==null)
throw new NullPointerException();
? ? this.callable = callable;
? ? this.state =NEW;? ? ? // ensure visibility of callable
}
get()方法的具體實現(xiàn),通過判斷starte是否是完成狀態(tài),如果state的值小于1,則調(diào)用awaitDone進行等待,其中awaitDone()方法有倆個值,第一個參數(shù)是否使用等待時間,如果設(shè)置false,線程沒有執(zhí)行完畢,則一致阻塞在哪里。第二個參數(shù)是設(shè)置等待的時間,與第一個結(jié)合使用。
? ??????public V get()throws InterruptedException, ExecutionException {
????????????int s =state;
? ? ????????if (s <=COMPLETING)
????????????????s = awaitDone(false, 0L);
? ? ????????????return report(s);
????????}
然后我們在看awaitDone()方法的具體實現(xiàn)。我們可以看到,首先通過一個無限循化是判斷線程是否被中斷,如果沒有被中斷,則s的值賦值為2.
然后繼續(xù)與COMPLETING進行比較,如果大于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);
? ? }
}
接下來我們看isDone()方法,該方法主要是獲取當(dāng)前線程的運行狀態(tài)結(jié)果,如果該線程運行結(jié)束則返回true,其中線程的結(jié)束有多種方式,包括線程的取消、異常或者正常結(jié)束都是線程的運行結(jié)束狀態(tài),在這里有好多初學(xué)者認為時只有線程正常運行結(jié)束后,才返回true,這是不正常的,尤其涉及線程運行結(jié)束獲取結(jié)果進行判斷時,一定要注意,下面我們通過多個例子進行演示獲取當(dāng)前線程運行結(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);
? ? }
}
首先我們通過Executors類創(chuàng)建個線程池,Executors后期我們會講解,他采用的是工廠方法模式。然后將線程提交到線程池,并將結(jié)果返回到Future中,在線程中我們只做個一個輸出“thread starting。。?!薄=酉聛砦覀儷@取當(dāng)前線程運行的結(jié)果狀態(tài),在這里我為了方便顯示不同情況下獲取線程運行狀態(tài)結(jié)果,通過一個循環(huán)獲取每次isDone()執(zhí)行的結(jié)果,通過控制臺我們可以看到,線程在運行過程中,第一次獲取false,這是由于異步執(zhí)行過程中,第一次循序時,線程沒有結(jié)束,所有獲取的結(jié)果為false,第二次循環(huán)中,線程已經(jīng)執(zhí)行完畢,所以獲取的結(jié)果為true。在實際運行中,不同電腦可能循環(huán)的次數(shù)不同。

在上面我們提到過,獲取線程結(jié)束狀態(tài),他包含的不單是正常結(jié)束的線程返回true,取消的線程,異常終止的線程也會返回true,總之一句話,就是線程結(jié)束了,那么他就會返回true,具體是怎么結(jié)束的,這不是他關(guā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)前線程運行結(jié)果狀態(tài) "+future.isDone());
? ? ? ? future.cancel(true);
? ? ? ? System.out.println("線程取消后獲取當(dāng)前線程運行結(jié)果狀態(tài) "+ future.isDone());
? ? }
}
上面的程序我提交一個線程到線程池。為了在線程執(zhí)行過程中我們能很直觀的看到線程執(zhí)行狀態(tài),我在循環(huán)打印1到100數(shù)據(jù)的時候,沒次間隔1秒,然后我們獲取當(dāng)前線程是否處于完成狀態(tài),在第一次執(zhí)行中,我們可以看到通過isDone方法返回false,表示當(dāng)前線程正常執(zhí)行,接著我們運行cancel()方法取消當(dāng)前的線程,然后在通過isDone()方法獲取當(dāng)前線程的運行狀態(tài),結(jié)果返回為true,通過上述示例我們可以看到isDone()方法返回當(dāng)前線程運行的結(jié)果狀態(tài),在返回ture的時候,他不但包含了線程的正常結(jié)束情況,也包括線程的取消或者異常情況。

最后一個方法我們來討論一下isCancelled() 方法,該方法表示在實際業(yè)務(wù)中用來判斷一個線程或者一個任務(wù)是否在完成前進行取消。
講在最后:在接下來的幾個章節(jié)中,我會用簡單的例子把常用的函數(shù)功能進行詳細解析透徹,隨著進一步的深入,我們將實際業(yè)務(wù)中的示例帶入進來,讓大家更加明了。