把Java 多線程說個透二

????????前一段時間寫了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ù)中的示例帶入進來,讓大家更加明了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 進程和線程 進程 所有運行中的任務(wù)通常對應(yīng)一個進程,當(dāng)一個程序進入內(nèi)存運行時,即變成一個進程.進程是處于運行過程中...
    勝浩_ae28閱讀 5,267評論 0 23
  • 進程和線程 進程 所有運行中的任務(wù)通常對應(yīng)一個進程,當(dāng)一個程序進入內(nèi)存運行時,即變成一個進程.進程是處于運行過程中...
    小徐andorid閱讀 2,996評論 3 53
  • 一.線程安全性 線程安全是建立在對于對象狀態(tài)訪問操作進行管理,特別是對共享的與可變的狀態(tài)的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 972評論 0 3
  • ??一個任務(wù)通常就是一個程序,每個運行中的程序就是一個進程。當(dāng)一個程序運行時,內(nèi)部可能包含了多個順序執(zhí)行流,每個順...
    OmaiMoon閱讀 1,809評論 0 12
  • 作為一名高三學(xué)生,從現(xiàn)在到高考也只剩了5個月的時間,那這5個月當(dāng)中怎么樣度過,才能更好的樹立信心,做好準(zhǔn)備去迎接這...
    鑫雨親子講師閱讀 895評論 0 3

友情鏈接更多精彩內(nèi)容