從零開始學習JAVA多線程(二)
前面已經簡單介紹程序和線程,為後續學習做鋪墊。本文讨論多線程傳參,Java多線程異常處理機制。
- 多線程的參數傳遞
在傳統開發過程中,我們習慣在調用函數時,将所需的參數傳入其中,通過函數内部邏輯處理傳回結果,大多情況下,整個過程均是由一條線程執行,排除運作不必要的的偶發性,似乎并不會出現意料之外的結果。而在多線程環境下,在使用線程時需要對線程進行一些必要的初始化,線程對這些資料進行處理後傳回結果,由于線程的運作和結束并不可控,線程傳參變得複雜起來,本文就以上問題介紹三種常用的傳遞參數方式。
(一)構造方法傳參
在建立線程時,需要建立一個Thread類的或者其子類的執行個體,通過調用其start()方法執行run()方法中的代碼塊,在此之前,我們可以通過構造函數傳遞線程運作所需要的資料,并使用變量儲存起來。代碼如下:
複制代碼
1 public class MyThread extends Thread {
2
3 //定義變量儲存參數
4 private String msg;
5
6 public MyThread() {
7 }
8
9 public MyThread(String msg) {
10 this.msg = msg;
11 }
12
13 //多線程的入口
14 public void run() {
15 System.out.println("MyThread " + msg);
16 }
17
18 public static void main(String[] args) {
19 MyThread myThread = new MyThread("is running");
20 myThread.start();
21 }
22 }
運作結果:
MyThread is running
Process finished with exit code 0
這種方式的優點很明顯:簡單、安全。線上程運作之前資料已經準備完成,避免線程丢失資料,如果傳遞更複雜資料,可以定義集合或者類等資料結構。缺點就是傳遞比較多的參數時,這種方式會使構造方法過于複雜,為了避免這種情況可以通過類方法和變量傳遞參數
(二)變量和類方法傳遞參數
在Thread執行個體類中定義需要傳遞的參數變量,并且定義一系列public的方法(或變量),在建立完Tread執行個體後通過調用方法給參數逐個指派。上面的代碼也可以通過定義setMsg()方法傳遞參數,代碼如下:
6 public void setMsg(String msg) {
7 this.msg = msg;
8 }
9
10 public MyThread() {
13 public MyThread(String msg) {
14 this.msg = msg;
15 }
16
17 //多線程的入口
18 public void run() {
19 System.out.println("MyThread " + msg);
20 System.out.println(Thread.currentThread().getThreadGroup());
22
23 public static void main(String[] args) {
24 MyThread myThread = new MyThread();
25 myThread.setMsg("is running");
26 myThread.start();
27 }
28 }
(三)通過回調函數傳遞資料
以上線程傳遞參數最常用的兩種方式,但是可以發現參數都在main方法中設定,然後Thread執行個體被動的接受參數,假如線上程運作中動态的擷取參數,如在run()方法先擷取三個随機數,通過Work類的process方法對這随機數求和,最後通過Data類的value值傳回結果。此例看出在傳回value之前,因為随機數的不确定性,我們并不能事先傳遞的值value。
View Code
上面代碼中process()方法即是回調函數,實質上是一個事件函數。整個事件流程為:将求和對象work傳入線程,線程執行過程中三個随機數的産生觸發了求和事件,通過傳遞進來的work對象調用process()方法,最後将結果傳回線程并在控制台輸出。這種傳遞資料的方式是在上面兩種傳參的基礎上進行了薄層封裝,并沒有直接将參數傳遞給線程,而是通過傳遞的對象進行邏輯處理之後将結果傳回。
- Java多線程異常處理機制
先來看對于未檢查異常在run()方法中是如何處理的。 運作結果: 由上可以看出對于未檢查異常,從線程将會直接宕掉,主線程繼續運作。那麼在主線程中能不能捕獲到異常呢?我們直接将全部代碼塊try catch起來 然後你會發現并沒有什麼卵用,主線程沒有捕獲到任何異常資訊,和未檢出異常如出一轍,從線程直接宕掉,主線程繼續運作 如果先檢查異常呢? IDEA提示: 結果還是讓人失望,程式還沒運作,IDEA已經提示報錯,原來run()方法本身不支援抛出異常的,方法重寫更不允許throws,是以run()方法不支援往外抛出異常。 最後再試下能不能在run()方法中try catch捕獲異常, 運作結果: 原來在run()方法中try catch是能捕捉到異常的。是以對于多線程已檢查的異常我們可以通過try catch進行處理,而對于未檢查的異常,如果沒有處理,一旦抛出該線程立馬宕掉,主線程則繼續運作,那麼未檢查的異常也全部需要try catch嗎?當然這也是一種方式。除此之外,Java還提供了異常處理器。
(一)Java異常處理器
在Java線程run()方法中,對于未檢查異常,借助于異常處理器進行處理。異常處理器可以直接了解為異常處理的方法,下面為具體如何使用。
UncaughtExceptionHandler,是Thread的内部接口。
Thread内部有兩個變量,用來記錄異常處理器。
Thread内部也分别提供了它們的get/set方法,set()方法其實沒什麼特别,主要是用來設定這兩個内部變量,重點在于它們的get()方法。
對于getUncaughtExceptionHandler方法,如果目前UncaughtExceptionHandler對象不為空,那麼直接傳回該對象,如果為空,傳回該線程所屬的線程組,由此得知,ThreadGroup實作了UncaughtExceptionHandler接口。
與此同時,内部實作了uncaughtException方法,
而對于getDefaultUncaughtExceptionHandler()方法,隻是簡單的傳回内部對象。
至此,我們可以了解到Thread内部有兩個異常處理器,分别提供了get/set方法,對于set方法隻是單純的設定異常處理器,對于get方法,getDefaultUncaughtExceptionHandler()方法直接擷取處理器;getUncaughtExceptionHandler()方法,進行判空,如果非空直接傳回,如果為空傳回該線程所屬的線程組,并且目前線程組是實作了Thread.UncaughtExceptionHandler接口,内部實作了public void uncaughtException(Thread t, Throwable e),其本質上它才是線程處理器。
對于defaultUncaughtExceptionHandler,表示應該程式預設的,整個程式可以使用的,它的get/set方法均為static修飾;對于uncaughtExceptionHandler,屬于執行個體方法,也就是說每個線程可以擁有一個,簡言之:每個線程都可以有一個uncaughtExceptionHandler,整個應用可以有一個defaultUncaughtExceptionHandler。它們之間是個體與全局的關系,如果個體擁有那麼就不再使用全局的;否則,走全局。這樣做的好處是非常靈活,既可以保證單個線程特别處理,又可以保障整個程式做到統一處理,在諸多場景發揮多種用處。
(二)異常處理邏輯
當run()方法中發生異常,JVM調用異常分發器,也就是借助getUncaughtExceptionHandler方法擷取異常處理器,然後執行它的uncaughtException方法。
是以關鍵之處在于uncaughtException()方法,再來看它的源碼:
如果已經設定異常處理器,那麼直接傳回,如果沒有設定,傳回目前線程組,并且調用線程組的uncaughtException方法時(如上圖),如果該線程組重寫了uncaughtException方法,直接調用;如果沒有,調用該線程組的父線程組;如果父線程組仍然沒有重寫,調用爺爺線程組,以此類推。但是如果所有的線程組都沒有重寫,進入else裡面,在else中擷取預設處理器,如果預設有,執行uncaughtException方法,如果沒有直接system.err。
以上就是異常處理器的處理邏輯,可以看出uncaughtExceptionHandler處理器優先級要高于defaultUncaughtException,這也符合就近原則,如果自身擁有了,又何必調用全局擁有的呢!
(三)異常處理器代碼示範
View Code
運作結果:
以上示例可以看出,盡管為檢查異常,通過異常處理器依然能夠感覺異常和資訊擷取,不會直接宕掉了。主要注意的是,必須在調用start()方法之前設定異常處理器,否則線程依舊直接宕掉。
(四)總結
Java多線程異常處理機制依賴于Thread内部兩個異常處理器,uncaughtExceptionHandler和defaultUncaughtExceptionHandler。兩者之間為個體和全局之間的關系,如果前者已經設定,那麼直接使用,否則使用後者。大緻步驟為:
a. 如果設定了異常處理器uncaughtExceptionHandler直接使用。
b. 如果沒設定,将會在祖先線程組中查找第一個重寫了uncaughtException的線程組,然後調用他的uncaughtException方法
c. 如果都沒有重寫,那麼使用應用預設的全局異常處理器defaultUncaughtExceptionHandler
d. 如果還是沒有設定,直接标準錯誤列印資訊
如果想要設定線程特有的異常處理器,可以調用set方法進行設定;如果想要對全局進行設定,可以調用靜态方法進行設定,需要注意的是必須要在調用start()方法之前設定。
原文位址
https://www.cnblogs.com/supiaol/p/10531156.html