日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

主线程如何等待多线程完成 返回数据_多线程基础体系知识清单

發(fā)布時(shí)間:2025/3/13 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 主线程如何等待多线程完成 返回数据_多线程基础体系知识清单 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者:Object

來源:https://juejin.im/user/5d53e1f6f265da03af19cae0/posts

前言

本文會介紹Java中多線程與并發(fā)的基礎(chǔ),適合初學(xué)者食用。

線程與進(jìn)程的區(qū)別

在計(jì)算機(jī)發(fā)展初期,每臺計(jì)算機(jī)是串行地執(zhí)行任務(wù)的,如果碰上需要IO的地方,還需要等待長時(shí)間的用戶IO,后來經(jīng)過一段時(shí)間有了批處理計(jì)算機(jī),其可以批量串行地處理用戶指令,但本質(zhì)還是串行,還是不能并發(fā)執(zhí)行。

如何解決并發(fā)執(zhí)行的問題呢?于是引入了進(jìn)程的概念,每個(gè)進(jìn)程獨(dú)占一份內(nèi)存空間,進(jìn)程是內(nèi)存分配的最小單位,相互間運(yùn)行互不干擾且可以相互切換,現(xiàn)在我們所看到的多個(gè)進(jìn)程“同時(shí)"在運(yùn)行,實(shí)際上是進(jìn)程高速切換的效果。

那么有了線程之后,我們的計(jì)算機(jī)系統(tǒng)看似已經(jīng)很完美了,為什么還要進(jìn)入線程呢?如果一個(gè)進(jìn)程有多個(gè)子任務(wù),往往一個(gè)進(jìn)程需要逐個(gè)去執(zhí)行這些子任務(wù),但往往這些子任務(wù)是不相互依賴的,可以并發(fā)執(zhí)行,所以需要CPU進(jìn)行更細(xì)粒度的切換。所以就引入了線程的概念,線程隸屬于某一個(gè)進(jìn)程,它共享進(jìn)程的內(nèi)存資源,相互間切換更快速。

進(jìn)程與線程的區(qū)別:

  • 進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位。所有與進(jìn)程相關(guān)的資源,均被記錄在PCB中。
  • 線程隸屬于某一個(gè)進(jìn)程,共享所屬進(jìn)程的資源。線程只由堆棧寄存器、程序計(jì)數(shù)器和TCB構(gòu)成。
  • 進(jìn)程可以看作獨(dú)立的應(yīng)用,線程不能看作獨(dú)立的應(yīng)用。
  • 進(jìn)程有獨(dú)立的地址空間,相互不影響,而線程只是進(jìn)程的不同執(zhí)行路徑,如果線程掛了,進(jìn)程也就掛了。所以多進(jìn)程的程序比多線程程序健壯,但是切換消耗資源多。
  • Java中進(jìn)程與線程的關(guān)系:

  • 運(yùn)行一個(gè)程序會產(chǎn)生一個(gè)進(jìn)程,進(jìn)程至少包含一個(gè)線程。
  • 每個(gè)進(jìn)程對應(yīng)一個(gè)JVM實(shí)例,多個(gè)線程共享JVM中的堆。
  • Java采用單線程編程模型,程序會自動創(chuàng)建主線程 。
  • 主線程可以創(chuàng)建子線程,原則上要后于子線程完成執(zhí)行。

  • 線程的start方法和run方法的區(qū)別

    區(qū)別

    Java中創(chuàng)建線程的方式有兩種,不管使用繼承Thread的方式還是實(shí)現(xiàn)Runnable接口的方式,都需要重寫run方法。調(diào)用start方法會創(chuàng)建一個(gè)新的線程并啟動,run方法只是啟動線程后的回調(diào)函數(shù),如果調(diào)用run方法,那么執(zhí)行run方法的線程不會是新創(chuàng)建的線程,而如果使用start方法,那么執(zhí)行run方法的線程就是我們剛剛啟動的那個(gè)線程。

    程序驗(yàn)證

    public?class?Main?{????public?static?void?main(String[]?args)?{????????Thread?thread?=?new?Thread(new?SubThread());????????thread.run();????????thread.start();????}}class?SubThread?implements?Runnable{????@Override????public?void?run()?{????????//?TODO?Auto-generated?method?stub????????System.out.println("執(zhí)行本方法的線程:"+Thread.currentThread().getName());????}}

    Thread和Runnable的關(guān)系

    Thread源碼

    Runnable源碼

    區(qū)別

    通過上述源碼圖,不難看出,Thread是一個(gè)類,而Runnable是一個(gè)接口,Runnable接口中只有一個(gè)沒有實(shí)現(xiàn)的run方法,可以得知,Runnable并不能獨(dú)立開啟一個(gè)線程,而是依賴Thread類去創(chuàng)建線程,執(zhí)行自己的run方法,去執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,才能讓這個(gè)類具備多線程的特性。

    使用繼承Thread方式和實(shí)現(xiàn)Runable接口方式分別創(chuàng)建子線程

    使用繼承Thread類方式創(chuàng)建子線程

    public?class?Main?extends?Thread{????public?static?void?main(String[]?args)?{????????Main?main?=?new?Main();????????main.start();????}????@Override????public?void?run()?{????????System.out.println("通過繼承Thread接口方式創(chuàng)建子線程成功,當(dāng)前線程名:"+Thread.currentThread().getName());????}}

    運(yùn)行結(jié)果:

    使用實(shí)現(xiàn)Runnable接口方式創(chuàng)建子線程

    public?class?Main{????public?static?void?main(String[]?args)?{????????SubThread?subThread?=?new?SubThread();????????Thread?thread?=?new?Thread(subThread);????????thread.start();????}}class?SubThread?implements?Runnable{????@Override????public?void?run()?{????????//?TODO?Auto-generated?method?stub????????System.out.println("通過實(shí)現(xiàn)Runnable接口創(chuàng)建子線程成功,當(dāng)前線程名:"+Thread.currentThread().getName());????}}

    運(yùn)行結(jié)果:

    使用匿名內(nèi)部類方式創(chuàng)建子線程

    public?class?Main{????public?static?void?main(String[]?args)?{????????Thread?thread?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????//?TODO?Auto-generated?method?stub????????????????System.out.println("使用匿名內(nèi)部類方式創(chuàng)建線程成功,當(dāng)前線程名:"+Thread.currentThread().getName());????????????}????????});????????thread.start();????}}

    運(yùn)行結(jié)果:

    關(guān)系

  • Thread是實(shí)現(xiàn)了Runnable接口的類,使得run支持多線程。
  • 因類的單一繼承原則,推薦使用Runnable接口,可以使程序更加靈活。

  • 如何實(shí)現(xiàn)處理多線程的返回值

    通過剛才的學(xué)習(xí),我們知道多線程的邏輯需要放到run方法中去執(zhí)行,而run方法是沒有返回值的,那么遇到需要返回值的狀況就不好解決,那么如何實(shí)現(xiàn)子線程返回值呢?

    主線程等待法

    通過讓主線程等待,直到子線程運(yùn)行完畢為止。

    實(shí)現(xiàn)方式:

    public?class?Main{????static?String?str;????public?static?void?main(String[]?args)?{????????Thread?thread?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????str="子線程執(zhí)行完畢";????????????}????????});????????thread.start();????????//如果子線程還未對str進(jìn)行賦值,則一直輪轉(zhuǎn)????????while(str==null)?{}????????System.out.println(str);????}}

    使用Thread中的join()方法

    join()方法可以阻塞當(dāng)前線程以等待子線程處理完畢。

    實(shí)現(xiàn)方式:

    public?class?Main{????static?String?str;????public?static?void?main(String[]?args)?{????????Thread?thread?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????str="子線程執(zhí)行完畢";????????????}????????});????????thread.start();????????//如果子線程還未對str進(jìn)行賦值,則一直輪轉(zhuǎn)????????try?{????????????thread.join();????????}?catch?(InterruptedException?e)?{????????????//?TODO?Auto-generated?catch?block????????????e.printStackTrace();????????}????????System.out.println(str);????}}

    join方法能做到比主線程等待法更精準(zhǔn)的控制,但是join方法的控制粒度并不夠細(xì)。比如,我需要控制子線程將字符串賦一個(gè)特定的值時(shí),再執(zhí)行主線程,這種操作join方法是沒有辦法做到的。

    通過Callable接口實(shí)現(xiàn):通過FutureTask或者線程池獲取

    在JDK1.5之前,線程是沒有返回值的,通常程序猿需要獲取子線程返回值頗費(fèi)周折,現(xiàn)在Java有了自己的返回值線程,即實(shí)現(xiàn)了Callable接口的線程,執(zhí)行了實(shí)現(xiàn)Callable接口的線程之后,可以獲得一個(gè)Future對象,在該對象上調(diào)用一個(gè)get方法,就可以執(zhí)行子線程的邏輯并獲取返回的Object。

    實(shí)現(xiàn)方式1(錯(cuò)誤示例):

    public?class?Main?implements?Callable{????@Override????public?String?call()?throws?Exception?{????????//?TODO?Auto-generated?method?stub????????String?str?=?"我是帶返回值的子線程";????????return?str;????}????public?static?void?main(String[]?args)?{????????Main?main?=?new?Main();????????try?{????????????String?str?=?main.call();????????????System.out.println(str);????????}?catch?(Exception?e)?{????????????//?TODO?Auto-generated?catch?block????????????e.printStackTrace();????????}????}}

    運(yùn)行結(jié)果:

    實(shí)現(xiàn)方式2(使用FutureTask):

    public?class?Main?implements?Callable{????@Override????public?String?call()?throws?Exception?{????????//?TODO?Auto-generated?method?stub????????String?str?=?"我是帶返回值的子線程";????????return?str;????}????public?static?void?main(String[]?args)?{????????FutureTask?task?=?new?FutureTask(new?Main());????????new?Thread(task).start();????????try?{????????????if(!task.isDone())?{????????????????System.out.println("任務(wù)沒有執(zhí)行完成");????????????}????????????System.out.println("等待中...");????????????Thread.sleep(3000);????????????System.out.println(task.get());????????}?catch?(InterruptedException?|?ExecutionException?e)?{????????????//?TODO?Auto-generated?catch?block????????????e.printStackTrace();????????}????}}

    運(yùn)行結(jié)果:

    實(shí)現(xiàn)方法3(使用線程池配合Future獲取):

    public?class?Main?implements?Callable{????@Override????public?String?call()?throws?Exception?{????????//?TODO?Auto-generated?method?stub????????String?str?=?"我是帶返回值的子線程";????????return?str;????}????public?static?void?main(String[]?args)?throws?InterruptedException,?ExecutionException?{????????ExecutorService?newCacheThreadPool?=?Executors.newCachedThreadPool();?????????Future?future?=?newCacheThreadPool.submit(new?Main());????????if(!future.isDone())?{????????????System.out.println("線程尚未執(zhí)行結(jié)束");????????}????????System.out.println("等待中");????????Thread.sleep(300);????????System.out.println(future.get());????????newCacheThreadPool.shutdown();????}}

    運(yùn)行結(jié)果:


    線程的狀態(tài)

    Java線程主要分為以下六個(gè)狀態(tài):新建態(tài)(new),運(yùn)行態(tài)(Runnable),無限期等待(Waiting),限期等待(TimeWaiting),阻塞態(tài)(Blocked),結(jié)束(Terminated)

    新建(new)

    新建態(tài)是線程處于已被創(chuàng)建但沒有被啟動的狀態(tài),在該狀態(tài)下的線程只是被創(chuàng)建出來了,但并沒有開始執(zhí)行其內(nèi)部邏輯。

    運(yùn)行(Runnable)

    運(yùn)行態(tài)分為Ready和Running,當(dāng)線程調(diào)用start方法后,并不會立即執(zhí)行,而是去爭奪CPU,當(dāng)線程沒有開始執(zhí)行時(shí),其狀態(tài)就是Ready,而當(dāng)線程獲取CPU時(shí)間片后,從Ready態(tài)轉(zhuǎn)為Running態(tài)。

    等待(Waiting)

    處于等待狀態(tài)的線程不會自動蘇醒,而只有等待被其它線程喚醒,在等待狀態(tài)中該線程不會被CPU分配時(shí)間,將一直被阻塞。以下操作會造成線程的等待:

  • 沒有設(shè)置timeout參數(shù)的Object.wait()方法。
  • 沒有設(shè)置timeout參數(shù)的Thread.join()方法。
  • LockSupport.park()方法(實(shí)際上park方法并不是LockSupport提供的,而是在Unsafe中,LockSupport只是對其做了一層封裝,可以看我的另一篇博客《鎖》,里面對于ReentrantLock的源碼解析有提到這個(gè)方法)。
  • 鎖:https://juejin.im/post/5d8da403f265da5b5d203bf4

    限期等待(TimeWaiting)

    處于限期等待的線程,CPU同樣不會分配時(shí)間片,但存在于限期等待的線程無需被其它線程顯式喚醒,而是在等待時(shí)間結(jié)束后,系統(tǒng)自動喚醒。以下操作會造成線程限時(shí)等待:

  • Thread.sleep()方法。
  • 設(shè)置了timeout參數(shù)的Object.wait()方法。
  • 設(shè)置了timeout參數(shù)的Thread.join()方法。
  • LockSupport.parkNanos()方法。
  • LockSupport.parkUntil()方法。
  • 阻塞(Blocked)

    當(dāng)多個(gè)線程進(jìn)入同一塊共享區(qū)域時(shí),例如Synchronized塊、ReentrantLock控制的區(qū)域等,會去整奪鎖,成功獲取鎖的線程繼續(xù)往下執(zhí)行,而沒有獲取鎖的線程將進(jìn)入阻塞狀態(tài),等待獲取鎖。

    結(jié)束(Terminated)

    已終止線程的線程狀態(tài),線程已結(jié)束執(zhí)行。


    Sleep和Wait的區(qū)別

    Sleep和Wait者兩個(gè)方法都可以使線程進(jìn)入限期等待的狀態(tài),那么這兩個(gè)方法有什么區(qū)別呢?

  • sleep方法由Thread提供,而wait方法由Object提供。
  • sleep方法可以在任何地方使用,而wait方法只能在synchronized塊或synchronized方法中使用(因?yàn)楸仨毇@wait方法會釋放鎖,只有獲取鎖了才能釋放鎖)。
  • sleep方法只會讓出CPU,不會釋放鎖,而wait方法不僅會讓出CPU,還會釋放鎖。
  • 測試代碼:

    public?class?Main{????public?static?void?main(String[]?args)?{????????Thread?threadA?=?new?Thread(new?ThreadA());????????Thread?threadB?=?new?Thread(new?ThreadB());????????threadA.setName("threadA");????????threadB.setName("threadB");????????threadA.start();????????threadB.start();????}????public?static?synchronized?void?print()?{????????System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行Sleep");????????try?{????????????Thread.sleep(1000);????????}?catch?(InterruptedException?e)?{????????????//?TODO?Auto-generated?catch?block????????????e.printStackTrace();????????}????????System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行Wait");????????try?{????????????Main.class.wait(1000);????????}?catch?(InterruptedException?e)?{????????????//?TODO?Auto-generated?catch?block????????????e.printStackTrace();????????}????????System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢");????}}class?ThreadA?implements?Runnable{????@Override????public?void?run()?{????????//?TODO?Auto-generated?method?stub????????Main.print();????}}class?ThreadB?implements?Runnable{????@Override????public?void?run()?{????????//?TODO?Auto-generated?method?stub????????Main.print();????}}

    執(zhí)行結(jié)果:

    從上面的結(jié)果可以分析出:當(dāng)線程A執(zhí)行sleep后,等待一秒被喚醒后繼續(xù)持有鎖,執(zhí)行之后的代碼,而執(zhí)行wait之后,立即釋放了鎖,不僅讓出了CPU還讓出了鎖,而后線程B立即持有鎖開始執(zhí)行,和線程A執(zhí)行了同樣的步驟,當(dāng)線程B執(zhí)行wait方法之后,釋放鎖,然后線程A拿到鎖打印了第一個(gè)執(zhí)行完畢,然后線程B打印執(zhí)行完畢。


    notify和notifyAll的區(qū)別

    notify

    notify可以喚醒一個(gè)處于等待狀態(tài)的線程,上代碼:

    public?class?Main{????public?static?void?main(String[]?args)?{????????Object?lock?=?new?Object();????????Thread?threadA?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????synchronized?(lock)?{????????????????????try?{????????????????????????lock.wait();????????????????????}?catch?(InterruptedException?e)?{????????????????????????//?TODO?Auto-generated?catch?block????????????????????????e.printStackTrace();????????????????????}????????????????????print();????????????????}????????????}????????});????????Thread?threadB?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????synchronized?(lock)?{????????????????????print();????????????????????lock.notify();????????????????}????????????}????????});????????threadA.setName("threadA");????????threadB.setName("threadB");????????threadA.start();????????threadB.start();????}????public?static?void?print()?{????????????System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行print");????????????try?{????????????????Thread.sleep(1000);????????????}?catch?(InterruptedException?e)?{????????????????//?TODO?Auto-generated?catch?block????????????????e.printStackTrace();????????????}????????????System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢");????}}

    執(zhí)行結(jié)果:

    代碼解釋:線程A在開始執(zhí)行時(shí)立即調(diào)用wait進(jìn)入無限等待狀態(tài),如果沒有別的線程來喚醒它,它將一直等待下去,所以此時(shí)B持有鎖開始執(zhí)行,并且在執(zhí)行完畢時(shí)調(diào)用了notify方法,該方法可以喚醒wait狀態(tài)的A線程,于是A線程蘇醒,開始執(zhí)行剩下的代碼。

    notifyAll

    notifyAll可以用于喚醒所有等待的線程,使所有處于等待狀態(tài)的線程都變?yōu)閞eady狀態(tài),去重新爭奪鎖。

    public?class?Main{????public?static?void?main(String[]?args)?{????????Object?lock?=?new?Object();????????Thread?threadA?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????synchronized?(lock)?{????????????????????try?{????????????????????????lock.wait();????????????????????}?catch?(InterruptedException?e)?{????????????????????????//?TODO?Auto-generated?catch?block????????????????????????e.printStackTrace();????????????????????}????????????????????print();????????????????}????????????}????????});????????Thread?threadB?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????synchronized?(lock)?{????????????????????print();????????????????????lock.notifyAll();????????????????}????????????}????????});????????threadA.setName("threadA");????????threadB.setName("threadB");????????threadA.start();????????threadB.start();????}????public?static?void?print()?{????????????System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行print");????????????try?{????????????????Thread.sleep(1000);????????????}?catch?(InterruptedException?e)?{????????????????//?TODO?Auto-generated?catch?block????????????????e.printStackTrace();????????????}????????????System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"執(zhí)行完畢");????}}

    執(zhí)行結(jié)果:

    要喚醒前一個(gè)例子中的線程A,不光notify方法可以做到,調(diào)用notifyAll方法同樣也可以做到,那么兩者有什么區(qū)別呢?

    區(qū)別

    要說清楚他們的區(qū)別,首先要簡單的說一下Java synchronized的一些原理,在openjdk中查看java的源碼可以看到,java對象中存在monitor鎖,monitor對象中包含鎖池和等待池。

    鎖池,假設(shè)有多個(gè)對象進(jìn)入synchronized塊爭奪鎖,而此時(shí)已經(jīng)有一個(gè)對象獲取到了鎖,那么剩余爭奪鎖的對象將直接進(jìn)入鎖池中。

    等待池,假設(shè)某個(gè)線程調(diào)用了對象的wait方法,那么這個(gè)線程將直接進(jìn)入等待池,而等待池中的對象不會去爭奪鎖,而是等待被喚醒。

    下面可以說notify和notifyAll的區(qū)別了:

    notifyAll會讓所有處于等待池中的線程全部進(jìn)入鎖池去爭奪鎖,而notify只會隨機(jī)讓其中一個(gè)線程去爭奪鎖。


    yield方法

    概念

    ????/**?????*?A?hint?to?the?scheduler?that?the?current?thread?is?willing?to?yield?????*?its?current?use?of?a?processor.?The?scheduler?is?free?to?ignore?this?????*?hint.?????*?????*?

    ?Yield?is?a?heuristic?attempt?to?improve?relative?progression?????*?between?threads?that?would?otherwise?over-utilise?a?CPU.?Its?use?????*?should?be?combined?with?detailed?profiling?and?benchmarking?to?????*?ensure?that?it?actually?has?the?desired?effect.?????*?????*?

    ?It?is?rarely?appropriate?to?use?this?method.?It?may?be?useful?????*?for?debugging?or?testing?purposes,?where?it?may?help?to?reproduce?????*?bugs?due?to?race?conditions.?It?may?also?be?useful?when?designing?????*?concurrency?control?constructs?such?as?the?ones?in?the?????*?{@link?java.util.concurrent.locks}?package.?????*/????public?static?native?void?yield();

    yield源碼上有一段長長的注釋,其大意是說:當(dāng)前線程調(diào)用yield方法時(shí),會給當(dāng)前線程調(diào)度器一個(gè)暗示,當(dāng)前線程愿意讓出CPU的使用,但是它的作用應(yīng)結(jié)合詳細(xì)的分析和測試來確保已經(jīng)達(dá)到了預(yù)期的效果,因?yàn)檎{(diào)度器可能會無視這個(gè)暗示,使用這個(gè)方法是不那么合適的,或許在測試環(huán)境中使用它會比較好。

    測試:

    public?class?Main{????public?static?void?main(String[]?args)?{????????Thread?threadA?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????System.out.println("ThreadA正在執(zhí)行yield");????????????????Thread.yield();????????????????System.out.println("ThreadA執(zhí)行yield方法完成");????????????}????????});????????Thread?threadB?=?new?Thread(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????System.out.println("ThreadB正在執(zhí)行yield");????????????????Thread.yield();????????????????System.out.println("ThreadB執(zhí)行yield方法完成");????????????}????????});????????threadA.setName("threadA");????????threadB.setName("threadB");????????threadA.start();????????threadB.start();????}

    測試結(jié)果:

    可以看出,存在不同的測試結(jié)果,這里選出兩張。

    第一種結(jié)果:線程A執(zhí)行完yield方法,讓出cpu給線程B執(zhí)行。然后兩個(gè)線程繼續(xù)執(zhí)行剩下的代碼。

    第二種結(jié)果:線程A執(zhí)行yield方法,讓出cpu給線程B執(zhí)行,但是線程B執(zhí)行yield方法后并沒有讓出cpu,而是繼續(xù)往下執(zhí)行,此時(shí)就是系統(tǒng)無視了這個(gè)暗示。


    interrupt方法

    中止線程

    interrupt函數(shù)可以中斷一個(gè)線程,在interrupt之前,通常使用stop方法來終止一個(gè)線程,但是stop方法過于暴力,它的特點(diǎn)是,不論被中斷的線程之前處于一個(gè)什么樣的狀態(tài),都無條件中斷,這會導(dǎo)致被中斷的線程后續(xù)的一些清理工作無法順利完成,引發(fā)一些不必要的異常和隱患,還有可能引發(fā)數(shù)據(jù)不同步的問題。

    溫柔的interrupt方法

    interrupt方法的原理與stop方法相比就顯得溫柔的多,當(dāng)調(diào)用interrupt方法去終止一個(gè)線程時(shí),它并不會暴力地強(qiáng)制終止線程,而是通知這個(gè)線程應(yīng)該要被中斷了,和yield一樣,這也是一種暗示,至于是否應(yīng)該中斷,由被中斷的線程自己去決定。當(dāng)對一個(gè)線程調(diào)用interrupt方法時(shí):

  • 如果該線程處于被阻塞狀態(tài),則立即退出阻塞狀態(tài),拋出InterruptedException異常。
  • 如果該線程處于running狀態(tài),則將該線程的中斷標(biāo)志位設(shè)置為true,被設(shè)置的線程繼續(xù)運(yùn)行,不受影響,當(dāng)運(yùn)行結(jié)束時(shí)由線程決定是否被中斷。

  • 線程池

    線程池的引入是用來解決在日常開發(fā)的多線程開發(fā)中,如果開發(fā)者需要使用到非常多的線程,那么這些線程在被頻繁的創(chuàng)建和銷毀時(shí),會對系統(tǒng)造成一定的影響,有可能系統(tǒng)在創(chuàng)建和銷毀這些線程所耗費(fèi)的時(shí)間會比完成實(shí)際需求的時(shí)間還要長。

    另外,在線程很多的狀況下,對線程的管理就形成了一個(gè)很大的問題,開發(fā)者通常要將注意力從功能上轉(zhuǎn)移到對雜亂無章的線程進(jìn)行管理上,這項(xiàng)動作實(shí)際上是非常耗費(fèi)精力的。

    利用Executors創(chuàng)建不同的線程池滿足不同場景的需求

    newFixThreadPool(int nThreads)指定工作線程數(shù)量的線程池。

    newCachedThreadPool()處理大量中斷事件工作任務(wù)的線程池,

  • 試圖緩存線程并重用,當(dāng)無緩存線程可用時(shí),就會創(chuàng)建新的工作線程。
  • 如果線程閑置的時(shí)間超過閾值,則會被終止并移出緩存。
  • 系統(tǒng)長時(shí)間閑置的時(shí)候,不會消耗什么資源。
  • newSingleThreadExecutor()創(chuàng)建唯一的工作線程來執(zhí)行任務(wù),如果線程異常結(jié)束,會有另一個(gè)線程取代它。可保證順序執(zhí)行任務(wù)。

    newSingleThreadScheduledExecutor()與newScheduledThreadPool(int corePoolSize)定時(shí)或周期性工作調(diào)度,兩者的區(qū)別在于前者是單一工作線程,后者是多線程

    newWorkStealingPool()內(nèi)部構(gòu)建ForkJoinPool,利用working-stealing算法,并行地處理任務(wù),不保證處理順序。

    Fork/Join框架:把大任務(wù)分割稱若干個(gè)小任務(wù)并行執(zhí)行,最終匯總每個(gè)小任務(wù)后得到大任務(wù)結(jié)果的框架。

    為什么要使用線程池

    線程是稀缺資源,如果無限制地創(chuàng)建線程,會消耗系統(tǒng)資源,而線程池可以代替開發(fā)者管理線程,一個(gè)線程在結(jié)束運(yùn)行后,不會銷毀線程,而是將線程歸還線程池,由線程池再進(jìn)行管理,這樣就可以對線程進(jìn)行復(fù)用。

    所以線程池不但可以降低資源的消耗,還可以提高線程的可管理性。

    使用線程池啟動線程

    public?class?Main{????public?static?void?main(String[]?args)?{????????ExecutorService?newFixThreadPool?=?Executors.newFixedThreadPool(10);????????newFixThreadPool.execute(new?Runnable()?{????????????@Override????????????public?void?run()?{????????????????//?TODO?Auto-generated?method?stub????????????????System.out.println("通過線程池啟動線程成功");????????????}????????});????????newFixThreadPool.shutdown();????}}

    新任務(wù)execute執(zhí)行后的判斷

    要知道這個(gè)點(diǎn)首先要先說說ThreadPoolExecutor的構(gòu)造函數(shù),其中有幾個(gè)參數(shù):

  • corePoolSize:核心線程數(shù)量。
  • maximumPoolSize:線程不夠用時(shí)能創(chuàng)建的最大線程數(shù)。
  • workQueue:等待隊(duì)列。
  • 那么新任務(wù)提交后會執(zhí)行下列判斷:

  • 如果運(yùn)行的線程少于corePoolSize,則創(chuàng)建新線程來處理任務(wù),即時(shí)線程池中的其它線程是空閑的。
  • 如果線程池中的數(shù)量大于等于corePoolSize且小于maximumPoolSize,則只有當(dāng)workQueue滿時(shí),才創(chuàng)建新的線程去處理任務(wù)。
  • 如果設(shè)置的corePoolSize和maximumPoolSize相同,則創(chuàng)建的線程池大小是固定的,如果此時(shí)有新任務(wù)提交,若workQueue未滿,則放入workQueue,等待被處理。
  • 如果運(yùn)行的線程數(shù)大于等于maximumPoolSize,maximumPoolSize,這時(shí)如果workQueue已經(jīng)滿了,則通過handler所指定的策略來處理任務(wù)。
  • handler 線程池飽和策略

    • AbortPolicy:直接拋出異常,默認(rèn)。
    • CallerRunsPolicy:用調(diào)用者所在的線程來執(zhí)行任務(wù)。
    • DiscardOldestPolicy:丟棄隊(duì)列中靠最前的任務(wù),并執(zhí)行當(dāng)前任務(wù)。
    • DiscardPolicy:直接丟棄任務(wù)
    • 自定義。

    線程池的大小如何選定

    這個(gè)問題并不是什么秘密,在網(wǎng)上各大技術(shù)網(wǎng)站均有文章說明,我就拿一個(gè)最受認(rèn)可的寫上吧

    • CPU密集型:線程數(shù) = 核心數(shù)或者核心數(shù)+1
    • IO密集型:線程數(shù) = CPU核數(shù)*(1+平均等待時(shí)間/平均工作時(shí)間)

    當(dāng)然這個(gè)也不能完全依賴這個(gè)公式,更多的是要依賴平時(shí)的經(jīng)驗(yàn)來操作,這個(gè)公式也只是僅供參考而已。


    結(jié)語

    本文提供了一些Java多線程和并發(fā)方面最最基礎(chǔ)的知識,適合初學(xué)者了解Java多線程的一些基本知識,如果想了解更多的關(guān)于并發(fā)方面的內(nèi)容可以看:

    https://juejin.im/post/5d8da403f265da5b5d203bf4

    總結(jié)

    以上是生活随笔為你收集整理的主线程如何等待多线程完成 返回数据_多线程基础体系知识清单的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。