您好,登錄后才能下訂單哦!
本篇內容主要講解“什么是多線程源碼”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“什么是多線程源碼”吧!
sleep是一個靜態方法,其有兩個重載方法,其中一個需要傳入毫秒數,另外一個既需 要毫秒數也需要納秒數。
sleep方法介紹
public static void sleep(long millis) throws InterruptedException public static void sleep(long millis, int nanos) throws InterruptedException
sleep方法會使當前線程進入指定毫秒數的休眠,暫停執行,雖然給定了一個休眠的 時間,但是最終要以系統的定時器和調度器的精度為準,休眠有一個非常重要的特性, 那就是其不會放棄monitor鎖的所有權(在后文中講解線程同步和鎖的時候會重點介紹 monitor),下面我們來看一個簡單的例子:
package com.wangwenjun,concurrent,chapter03;public class Threadsleeppublic static void main(String[] args)new Thread(()->long startTime = System.currentTimeMillis();sleep(2_000L);long endTime = System.currentTimeMillis();System.out.printin(String.format("Total spend %d ms", (endTime - startTime)));}).start();long startTime = System.currentTimeMillis();sleep(3_000L);long endTime = System.currentTimeMillis();System.out.printin(String.format("Main thread total spend %d ms", (endTime - startTime)));}private static void sleep(long ms){try{Thread.sleep(ms);} catch (InterruptedException e){}}}
在上面的例子中,我們分別在自定義的線程和主線程中進行了休眠,每個線程的休眠 互不影響,Thread.sleep只會導致當前線程進入指定時間的休眠。
使用 TimeUnit 替代 Thread.sleep
在JDK1.5以后,JDK引入了一個枚舉TimeUnit,其對sleep方法提供了很好的封裝, 使用它可以省去時間單位的換算步驟,比如線程想休眠3小時24分17秒88毫秒,使用 TimeUnit來實現就非常的簡便優雅了 :
Thread.sleep(12257088L);TimeUnit.HOURS.sleep(3);TimeUnit? MINUTES.sleep(24);TimeUnit, SECONDS? sleep(17);TimeUnit.MILLISECONDS.sleep(88);
同樣的時間表達,TimeUnit顯然清晰很多,筆者強烈建議,在使用Thread.sleep的地 方,完全使用TimeUnit來代替,因為sleep能做的事,TimeUnit全部都能完成,并且功能 更加的強大,在本書后面的內容中,我將全部采用TimeUnit替代sleep。
yield方法介紹
yield方法屬于一種啟發式的方法,其會提醒調度器我愿意放棄當前的CPU資源,如果 CPU的資源不緊張,則會忽略這種提醒。
調用yield方法會使當前線程從RUNNING狀態切換到RUNNABLE狀態,一般這個方 法不太常用:
package com.wangwenjun.concurrent,chapter03;import java.util.stream.IntStream;public class ThreadYield{public static void main(String[] args){IntStream.range(0, 2).mapToObj(ThreadYield::create).forEach(Thread::start);}private static Thread create(int index){return new Thread(() ->{//①注釋部分//if (index == 0)// Thread, yield();System.out.printin(index);});}}
上面的程序運行很多次,你會發現輸出的結果不一致,有時候是0最先打印出來,有
時候是1最先打印出來,但是當你打開代碼的注釋部分,你會發現,順序始終是0, 10 因為第一個線程如果最先獲得了 CPU資源,它會比較謙虛,主動告訴CPU調度器是 放了原本屬于自己的資源,但是yield R是一個提示(hint), CPU調度器并不會擔保每次都 能滿足yield提示。
yield sleep
看過前面的內容之后,會發現yield和sleep有一些混淆的地方,在JDK1.5以前的版本 中yield的方法事實上是調用了 sleep(O),但是它們之間存在著本質的區別,具體如下。
□ sleep會導致當前線程暫停指定的時間,沒有CPU時間片的消耗。
□ yield只是對CPU調度器的一個提示,如果CPU調度器沒有忽略這個提示,它會導
致線程上下文的切換。
□ sleep會使線程短暫block,會在給定的時間內釋放CPU資源。
□ yield會使RUNNING狀態的Thread進入RUNNABLE狀態(如果CPU調度器沒有 忽略這個提示的話)。
□ sleep幾乎百分之百地完成了給定時間的休眠,而yield的提示并不能一定擔保。
□ 一個線程sleep另一個線程調用interrupt會捕獲到中斷信號,而yield則不會。
設置線程的優先級
□ public final void setPriority(int newPriority)為線程設定優先級。
□ public final int getPriority()獲取線程的優先級。
線程優先級介紹
進程有進程的優先級,線程同樣也有優先級,理論上是優先級比較高的線程會獲取優 先被CPU調度的機會,但是事實上往往并不會如你所愿,設置線程的優先級同樣也是一個 hint操作,具體如下。
□對于root用戶,它會hint操作系統你想要設置的優先級別,否則它會被忽略。
□如果CPU比較忙,設置優先級可能會獲得更多的CPU時間片,但是閑時優先級的 高低幾乎不會有任何作用。
所以,不要在程序設計當中企圖使用線程優先級綁定某些特定的業務,或者讓業務嚴 重依賴于線程優先級,這可能會讓你大失所望。舉個簡單的例子,可能不同情況下的運行 效果不會完全一樣,但是我們只是想讓優先級比較高的線程獲得更多的信息輸出機會,示 例代碼如下:
package com.wangwenjun.concurrent.chapter03;public class Threadpriority{public static void main(String[] args){Thread tl = new Thread(()->{while (true){System.out.printin("tl");}});tl? setPriority(3);Thread t2 = new Thread(()->{while (true){System. out. printin (,lt2");}});t2.setPriority(10);tl.start();t2.start();}}
運行上面的程序,會發現t2出現的頻率很明顯要高一些,當然這也和筆者當前CPU的 資源情況有關系,不同情況下的運行會有不一樣的結果。
線程優先級源碼分析
設置線程的優先級,只需要調用setPriority方法即可,下面我們打開Thread的源碼, 一起來分析一下:
public final void setPriority(int newPriority) {ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException();}if((g = getThreadGroup()) 1= null) {if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority();}setPriorityO(priority = newPriority);}}
通過上面源碼的分析,我們可以看出,線程的優先級不能小于1也不能大于10,如果 指定的線程優先級大于線程所在group的優先級,那么指定的優先級將會失效,取而代之 的是group的最大優先級,下面我們通過一個例子來證明一下:
package com.wangwenjun.concurrent.chapter03;public class Threadpriority{public static void main(String[] args){//定義一個線程組ThreadGroup group = new ThreadGroup("test");//將線程組的優先級指定為7group.setMaxPriority(7);//定義一個線程,將該線程加入到group中Thread thread = new Thread(group, "test-thread");//企圖將線程的優先級設定為10thread.setPriority(10);/ /企圖未遂System.out.printin(thread.getPriority());}}
上面的結果輸出為7,而不是10,因為它超過了所在線程組的優先級別
關于優先級的一些總結
一般情況下,不會對線程設定優先級別,更不會讓某些業務嚴重地依賴線程的優先級 別,比如權重,借助優先級設定某個任務的權重,這種方式是不可取的,一般定義線程的 時候使用默認的優先級就好了,那么線程默認的優先級是多少呢?
線程默認的優先級和它的父類保持一致,一般情況下都是5,因為main線程的優先級 就是5,所以它派生出來的線程都是5,示例代碼如下:
package com.wangwenjun.concurrent.chapter03;public class Threadpriority{public static void main(String[] args){Thread tl = new Thread();System.out,println("tl priority " + tl.getPriority());Thread t2 = new Thread(()->{Thread t3 = new Thread();System.out.printIn("t3 priority " + t3.getPriority());});t2 ? setPriority(6);t2 ? start();System.out.printin("t2 priority " + t2.getPriority());
上面程序的輸出結果是tl的優先級為5,因為main線程的優先級是5 ; t2的優先級 是6,因為顯式地將其指定為6; t3的優先級為6,沒有顯式地指定,因此其與父線程保持 一致。
public long getld()獲取線程的唯一 ID,線程的ID在整個JVM進程中都會是唯一的,
并且是從0開始逐次遞增。如果你在main線程(main函數)中創建了一個唯一的線程,并 且調用getld()后發現其并不等于0,也許你會納悶,不應該是從0開始的嗎?之前已經說 過了在一個JVM進程啟動的時候,實際上是開辟了很多個線程,自增序列已經有了一定的 消耗,因此我們自己創建的線程絕非第0號線程。
public static Thread currentThread()用于返回當前執行線程的引用,這個方法雖然很簡 單,但是使用非常廣泛,我們在后面的內容中會大量的使用該方法,來看一段示例代碼:
package com.wangwenjun.concurrent.chapter03;public class CurrentThread{public static void main(String[] args){Thread thread = new Thread(){@Overridepublic void run(){//always true System.out.printin(Thread.CurrentThread() == this);}};thread, start();String name = Thread.CurrentThread().getName(); System.out.printIn("main".equals(name));}}
上面程序運行輸出的兩個結果都是true。
□ public ClassLoader getContextClassLoader()獲取線程上下文的類加載器,簡單來說 就是這個線程是由哪個類加器加載的,如果是在沒有修改線程上下文類加載器的情 況下,則保持與父線程同樣的類加載器。
□ public void setContextClassLoader(ClassLoader cl)設置該線程的類加載器,這個方法 可以打破JAVA類加載器的父委托機制,有時候該方法也被稱為JAVA類加載器的 后門。
關于線程上下文類加載器的內容我們將在本書的第11章重點介紹,并且結合jdbc驅動 包的源碼分析JDK的開發者為什么要留有這樣的后門。
線程interrupt,是一個非常重要的API,也是經常使用的方法,與線程中斷相關的API 有如下幾個,在本節中我們也將Thread深入源碼對其進行詳細的剖析。
□ public void interrupt()□ public static boolean interrupted()□ public boolean islnterrupted()
interrupt
如下方法的調用會使得當前線程進入阻塞狀態,而調用當前線程的interrupt方法,就 可以打斷阻塞。
□ Object 的 wait 方法。□ Object 的 wait(long)方法。□ Object 的 wait(long,int)方法。□ Thread 的 sleep(long)方法。□ Thread 的 sleep(long,int)方法。□ Thread 的 join 方法。□ Thread 的 join(long)方法。□ Thread 的 join(long,int)方法。□ InterruptibleChannel 的 io 操作。□ Selector 的 wakeup 方法。
□其他方法。
上述若干方法都會使得當前線程進入阻塞狀態,若另外的一個線程調用被阻塞線程的 interrupt方法,則會打斷這種阻塞,因此這種方法有時會被稱為可中斷方法,.記住,打斷一 個線程并不等于該線程的生命周期結束,僅僅是打斷了當前線程的阻塞狀態。
一旦線程在阻塞的情況下被打斷,都會拋出一個稱為InterruptedException的異常,這 個異常就像一個signal (信號)一樣通知當前線程被打斷了,下面我們來看一個例子:
package com.wangwenjun,concurrent,chapter03;import java.util.concurrent .TimeUnit;public class Threadinterruptpublic static void main(String[] args) throws InterruptedException{Thread thread = new Thread(()->{try{TimeUnit?MINUTES.sleep(1);} catch (InterruptedException e){System.out.printIn("Oh, i am be interrupted.");} 、});thread.start();//short block and make sure thread is started.TimeUnit, MILLISECONDS.sleep(2);thread, interrupt();}}
上面的代碼創建了一個線程,并且企圖休眠1分鐘的時長,不過很可惜,大約在2毫 秒之后就被主線程調用interrupt方法打斷,程序的執行結果就是“Oh, i am be interrupted.”
interrupt這個方法到底做了什么樣的事情呢?在一個線程內部存在著名為interrupt flag 的標識,如果一個線程被interrupt,那么它的flag將被設置,但是如果當前線程正在執行 可中斷方法被阻塞時,調用interrupt方法將其中斷,反而會導致flag被清除,關于這點我 們在后面還會做詳細的介紹。另外有一點需要注意的是,如果一個線程已經是死亡狀態, 那么嘗試對其的interrupt會直接被忽略。
islnterrupted
islnterrupted是Thread的一個成員方法,它主要判斷當前線程是否被中斷,該方法僅 僅是對interrupt標識的一個判斷,并不會影響標識發生任何改變,這個與我們即將學習到 的interrupted是存在差別的,下面我們看一個簡單的程序:
package com.wangwenjun,concurrent?chapter03;import java.util.concurrent.TimeUnit;public class ThreadisInterrupted{public static void main(String[] args) throws InterruptedExceptionThread thread = new Thread(){@Overridepublic void run(){while (true){//do nothing, just empty loop.}}};thread.start();TimeUnit.MILLISEC0NDS.sleep(2);System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted()); thread.interrupt();System.out.printf("Thread is interrupted ? %s\n", thread.islnterrupted());}}
上面的代碼中定義了一個線程,并且在線程的執行單元中(run方法)寫了一個空的死 循環,為什么不寫sleep呢?因為sleep是可中斷方法,會捕獲到中斷信號,從而干擾我們 程序的結果。下面是程序運行的結果,記得手動結束上面的程序運行,或者你也可以將上 面定義的線程指定為守護線程,這樣就會隨著主線程的結束導致JVM中沒有非守護線程而 自動退出。
Thread is interrupted ? falseThread is interrupted ? true
可中斷方法捕獲到了中斷信號(signal)之后,也就是捕獲了 InterruptedException異常 之后會擦除掉interrupt的標識,對上面的程序稍作修改,你會發現程序的結果又會出現很 大的不同,示例代碼如下:
package com.wangwenjun.concurrent.chapter03;import java.util.concurrent.TimeUnit;public class ThreadisInterrupted{public static void main(String[] args) throws InterruptedException{Thread thread = new Thread(){@Overridepublic void run()while (true)try{TimeUnit.MINUTES.sleep(1);} catch (InterruptedException e){//ignore the exception//here the interrupt flag will be clear.System.out.printf("I am be interrupted ? %s\n", islnterrupted());}}}};thread.setDaemon(true);thread.start();TimeUnit.MILLISECONDS.sleep(2);System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted()); thread.interrupt();TimeUnit, MILLISECONDS? sleep(2); System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted());}}
由于在run方法中使用了 sleep這個可中斷方法,它會捕獲到中斷信號,并且會擦除 interrupt標識,因此程序的執行結果都會是false,程序輸岀如下:
Thread is interrupted ? falseI am be interrupted ? falseThread is interrupted ? false
其實這也不難理解,可中斷方法捕獲到了中斷信號之后,為了不影響線程中其他方法 的執行,將線程的interrupt標識復位是一種很合理的設計。
interrupted
interrupted是一個靜態方法,雖然其也用于判斷當前線程是否被中斷,但是它和成員方 法islnterrupted還是有很大的區別的,調用該方法會直接擦除掉線程的interrupt標識,需 要注意的是,如果當前線程被打斷了,那么第一次調用interrupted方法會返回true,并且 立即擦除了 interrupt標識;第二次包括以后的調用永遠都會返回false,除非在此期間線程 又一次地被打斷,下面設計了一個簡單的例子,來驗證我們的說法:
package com.wangwenjun.concurrent.chapter03; import java.util.concurrent.TimeUnit; public class Threadinterrupted public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { while (true) { System.out.printIn(Thread.interrupted()); } } }; thread.setDaemon(true); thread.start(); //shortly block make sure the thread is started. TimeUnit, MILLISECONDS.sleep(2); thread.interrupt(); } }
同樣由于不想要受到可中斷方法如sleep的影響,在Thread的run方法中沒有進行任 何短暫的休眠,所以運行上面的程序會出現非常多的輸出,但是我們通過對輸出的檢查會 發現如下所示的內容,其足以作為對該方法的解釋。
false false true false false
在很多的false包圍中發現了一個true,也就是interrupted方法判斷到了其被中斷,立 即擦除了中斷標識,并且只有這一次返回true,后面的都將會是false。
interrupt 注意事項
打開Thread的源碼,不難發現,islnterrupted方法和interrupted方法都調用了同一個 本地方法:
private native boolean islnterrupted(boolean Clearlnterrupted);
其中參數Clearlnterrupted主要用來控制是否擦除線程interrupt的標識。
islnterrupted方法的源碼中該參數為false,表示不想擦除:
public boolean islnterrupted() { return islnterrupted(false); }
而interrupted靜態方法中該參數則為true,表示想要擦除:
public static boolean interrupted() { return currentThread().islnterrupted(true); }
在比較詳細地學習了 interrupt方法之后,大家思考一個問題,如果一個線程在沒有執 行可中斷方法之前就被打斷,那么其接下來將執行可中斷方法,比如sleep會發生什么樣的 情況呢?下面我們通過一個簡單的實驗來回答這個疑問:
public static void main(String!] args) { //① 判斷當前線程是否被中斷 System.out.printin("Main thread is interrupted? " + Thread.interrupted()); //②中斷當前線程 Thread.currentThread().interrupt(); //③判斷當前線程是否已經被中斷 System.out.printin("Main thread is interrupted? " + Thread.currentThread(). islnterrupted()); try { //④ 當前線程執行可中斷方法 TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { //⑤捕獲中斷信號 System.out.printin("I will be interrupted still."); } }
通過運行上面的程序,你會發現,如果一個線程設置了 interrupt標識,那么接下來的 可中斷方法會立即中斷,因此注釋⑤的信號捕獲部分代碼會被執行,請大家注意注釋①和注 釋③中判斷線程中斷方法的不同,也希望讀者結合本節的內容思考為什么要這么做?
Thread的join方法同樣是一個非常重要的方法,使用它的特性可以實現很多比較強大 的功能,與sleep -樣它也是一個可中斷的方法,也就是說,如果有其他線程執行了對當前 線程的interrupt操作,它也會捕獲到中斷信號,并且擦除線程的interrupt標識,Thread的 API為我們提供了三個不同的join方法,具體如下。
□ public final void join() throws InterruptedException □ public final synchronized void join(long millis, int nanos) throws InterruptedException □ public final synchronized void join(long millis) throws InterruptedException
在本節中,筆者將會詳細介紹join方法以及如何在實際應用中使用join方法。
線程join方法詳解
join某個線程A,會使當前線程B進入等待,直到線程A結束生命周期,或者到達給 定的時間,那么在此期間B線程是處于BLOCKED的,而不是A線程,下面就來通過一個 簡單的實例解釋一下join方法的基本用法:
package com.wangwenjun.concurrent.chapter03; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import static java.util.stream.Collectors.toList; public class Threadjoin { public static void main(String[] args) throws InterruptedException { //①定義兩個線程,并保存在threads中 List<Thread> threads = IntStream.range(1, 3) .mapToObj(Threadjoin::create).collect(toList()); //②啟動這兩個線程 threads?forEach(Thread::start); //③ 執行這兩個線程的join方法 for (Thread thread : threads) { thread.join(); } //④main線程循環輸出 for (int i = 0; i < 10; i++) System.out.printin(Thread.currentThread().getName() + "+ i); shortSleep(); //構造一個簡單的線程,每個線程只是簡單的循環輸出 private static Thread create(int seq) { return new Thread(() -> { for (int i = 0; i < 10; i++) { System? out? printin(Thread.currentThread()? getName() + "#" + i); shortSleep(); } }, String.valueOf(seq)); } private static void shortSleep() { try { TimeUnit, SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面的代碼結合Java 8的語法,創建了兩個線程,分別啟動,并且調用了每個線程的 join方法(注意:join方法是被主線程調用的,因此在第一個線程還沒有結束生命周期的時 后,第二個線程的join不會得到執行,但是此時,第二個線程也已經啟動了),運行上面的 程序,你會發現線程一和線程二會交替地輸出直到它們結束生命周期,main線程的循環才 會開始運行,程序輸岀如下:
2#8 1#8 2#9 1#9 main#0 main#l main#2 main#3
如果你將注釋③下面的join全部注釋掉,那么三個線程將會交替地輸出,程序輸出如下:
main#2 2#2 1#2 main#3 1#3 2#3 main#4
join方法會使當前線程永遠地等待下去,直到期間被另外的線程中斷,或者join的線 程執行結束,當然你也可以使用join的另外兩個重載方法,指定毫秒數,在指定的時間到 達之后,當前線程也會退出阻塞。同樣思考一個問題,如果一個線程已經結束了生命周期, 那么調用它的join方法的當前線程會被阻塞嗎?
join方法結合實戰
本節我們將結合一個實際的案例,來看一下join方法的應用場景,假設你有一個APP, 主要用于查詢航班信息,你的APP是沒有這些實時數據的,當用戶發起查詢請求時,你需 要到各大航空公司的接口獲取信息,最后統一整理加工返回到APP客戶端,如圖3-1所示, 當然JDK自帶了很多高級工具,比如CountDownLatch和CyclicBarrier等都可以完成類似 的功能,但是僅就我們目前所學的知識,使用join方法即可完成下面的功能。
該例子是典型的串行任務局部并行化處理,用戶在APP客戶端輸入出發地“北京”和 目的地“上海”,服務器接收到這個請求之后,先來驗證用戶的信息,然后到各大航空公司 的接口查詢信息,最后經過整理加工返回給客戶端,每一個航空公司的接口不會都一樣, 獲取的數據格式也不一樣,查詢的速度也存在著差異,如果再跟航空公司進行串行化交互 (逐個地查詢),很明顯客戶端需要等待很長的時間,這樣的話,用戶體驗就會非常差。如果 我們將每一個航空公司的查詢都交給一個線程去工作,然后在它們結束工作之后統一對數 據進行整理,這樣就可以極大地節約時間,從而提高用戶體驗效果。
代碼清單3-1 查詢接口 FightQuery
package com.wangwenjun.concurrent.chapter03; import j ava.util.List; public interface FightQuery { List<String> get(); }
在代碼清單3-1中,FightQuery提供了一個返回方法,寫到這里大家應該注意到了,不 管是Thread的run方法,還是Runnable接口,都是void返回類型,如果你想通過某個線 程的運行得到結果,就需要自己定義一個返回的接口。
查詢Fight的task,其實就是一個線程的子類,主要用于到各大航空公司獲取數據,示 例代碼如下:
package com.wangwenjun,concurrent.chapter03; import java.util.ArrayList; import j ava.util.List; import j ava.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class FightQueryTask extends Thread implements FightQuery public FightQueryTask(String airline, String origin, String destination) super("[">接口定義好了,查詢航班數據的線程也有了,下面就來實現一下從SH (上海)到北京 (BJ)的航班查詢吧!示例代碼如下:package com.wangwenjun.concurrent.chapter0 3; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static java.util.stream.Collectors.toList; public class FightQueryExample { //①合作的各大航空公司 private static List<String> fightCompany = Arrays.asList( "CSA", "CEA", "HNA" ); public static void main(String[] args) { List<String> results = search("SH", "BJ"); System.out.printin("===========result==========="); results , forEach(System?out::printin); } private static List<String> search(String original, String dest) { final List<String> result = new ArrayList<>(); //②創建查詢航班信息的線程列表 List<FightQueryTask> tasks = fightCompany.stream() .map(f -> createSearchTask(f, original, dest)) .collect(toList()); //③分別啟動這幾個線程 tasks , forEach(Thread::start); 〃④分別調用每一個線程的join方法,阻塞當前線程 tasks.forEach(t -> try t.join(); } catch (InterruptedException e) { } }); //⑤在此之前,當前線程會阻塞住,獲取每一個查詢線程的結果,并且加入到result中 tasks.stream().map(FightQuery::get).forEach(result::addAll); return result; } FightQueryTask createSearchTask( fight, original, String dest) return new FightQueryTask(fight, original, dest); } 上面的代碼,關鍵的地方已通過注釋解釋得非常清楚,主線程收到了 search請求之后, 交給了若干個查詢線程分別進行工作,最后將每一個線程獲取的航班數據進行統一的匯總。 由于每個航空公司的查詢時間可能不一樣,所以用了一個隨機值來反應不同的查詢速度, 返回給客戶端(打印到控制臺),程序的執行結果輸出如下:[CSA]-query from SH to BJ [CEA]-query from SH to BJ [HNA]-query from SH to BJ The Fight:[HNA] list query The Fights[CSA] list query The Fight:[CEA] list query ===========result=========: [CSA]-4 [CEA]-7 [HNA]-2 如何關閉一?個線程JDK有一個Deprecated方法stop,但是該方法存在一個問題,JDK官方早已經不推薦 使用,其在后面的版本中有可能會被移除,根據官網的描述,該方法在關閉線程時可能不 會釋放掉monitor的鎖,所以強烈建議不要使用該方法結束線程,本節將主要介紹幾種關閉 線程的方法。正常關閉\1. 線程結束生命周期正常結束線程運行結束,完成了自己的使命之后,就會正常退出,如果線程中的任務耗時比較 短,或者時間可控,那么放任它正常結束就好了。\2. 捕獲中斷信號關閉線程我們通過new Thread的方式創建線程,這種方式看似很簡單,其實它的派生成本是比 較高的,因此在一個線程中往往會循環地執行某個任務,比如心跳檢查,不斷地接收網絡 消息報文等,系統決定退出的時候,可以借助中斷線程的方式使其退出,示例代碼如下:package com.wangwenjun.concurrent.chapter0 3; import java.util.concurrent. TimeUnit; public class InterruptThreadExit { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { System.out.printin("I will start work"); while (!islnterrupted()) { //working. } System.out.printin("I will be exiting."); } }; t.start(); TimeUnit.MINUTES? sleep(1); System.out.printin("System will be shutdown."); t.interrupt(); } } 上面的代碼是通過檢查線程interrupt的標識來決定是否退出的,如果在線程中執行某 個可中斷方法,則可以通過捕獲中斷信號來決定是否退出。@Override public void run() System.out.printin("I will start work"); for (;;) //working, try TimeUnit.MILLISECONDS.sleep(l); catch (InterruptedException e) break; } } System.out.printin("I will be exiting."); } 上面的代碼執行結果都會導致線程正常的結束,程序輸出如下:I will start work System will be shutdown. I will be exiting. \3. 使用volatile開關控制由于線程的interrupt標識很有可能被擦除,或者邏輯單元中不會調用任何可中斷方法, 所以使用volatile修飾的開關flag關閉線程也是一種常用的做法,具體如下:package com.wangwenjun.concurrent.chapter03; import java.util?concurrent.TimeUnit; public class FlagThreadExit static class MyTask extends Thread private volatile boolean closed = false; @Override public void run() System.out.printIn("I will start work"); while (Iclosed && !islnterrupted()) //正在運行 } System.out.printin("I will be exiting."); public void close() { this.closed = true; this , interrupt(); } ? public static void main(String[] args) throws InterruptedException { MyTask t = new MyTask(); t.start(); TimeUnit? MINUTES.sleep(1); System.out.printIn("System will be shutdown."); t.close(); } } 上面的例子中定義了一個closed開關變量,并且是使用volatile修飾(關于volatile關 鍵字會在本書的第3部分中進行非常細致地講解,volatile關鍵字在Java中是一個革命性的 關鍵字,非常重要,它是Java原子變量以及并發包的基礎)運行上面的程序同樣也可以關 閉線程。異常退出在一個線程的執行單元中,是不允許拋出checked異常的,不論Thread中的run方 法,還是Runnable中的run方法,如果線程在運行過程中需要捕獲checked異常并且 判斷是否還有運行下去的必要,那么此時可以將checked異常封裝成unchecked異常 (RuntimeException)拋出進而結束線程的生命周期。
到此,相信大家對“什么是多線程源碼”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。