前面已經簡單介紹進程和線程,爲後續學習作鋪墊。本文討論多線程傳參,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 }
運行結果:dom
MyThread is running
Process finished with exit code 0
這種方式的優勢很明顯:簡單、安全。在線程運行以前數據已經準備完成,避免線程丟失數據,若是傳遞更復雜數據,能夠定義集合或者類等數據結構。缺點就是傳遞比較多的參數時,這種方式會使構造方法過於複雜,爲了不這種狀況能夠經過類方法和變量傳遞參數ide
在Thread實例類中定義須要傳遞的參數變量,而且定義一系列public的方法(或變量),在建立完Tread實例後經過調用方法給參數逐個賦值。上面的代碼也能夠經過定義setMsg()方法傳遞參數,代碼以下:函數
1 public class MyThread extends Thread { 2 3 //定義變量保存參數 4 private String msg; 5 6 public void setMsg(String msg) { 7 this.msg = msg; 8 } 9 10 public MyThread() { 11 } 12 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()); 21 } 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。學習
1 class Data { 2 public int value = 0; 3 } 4 5 class Work { 6 //回調函數 7 public void process(Data data, Integer[] numbers) { 8 for (int n : numbers) { 9 data.value += n; 10 } 11 } 12 } 13 14 public class MyThread extends Thread { 15 //回調函數的對象 16 private Work work; 17 18 public MyThread(Work work) { 19 this.work = work; 20 } 21 22 public void run() { 23 java.util.Random random = new java.util.Random(); 24 Data data = new Data(); 25 int n1 = random.nextInt(1000); 26 int n2 = random.nextInt(2000); 27 int n3 = random.nextInt(3000); 28 work.process(data, new Integer[]{n1, n2, n3}); // 使用回調函數 29 System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" 30 + String.valueOf(n3) + "=" + data.value); 31 } 32 33 public static void main(String[] args) { 34 Thread thread = new MyThread(new Work()); 35 thread.start(); 36 } 37 38 }
上面代碼中process()方法便是回調函數,實質上是一個事件函數。整個事件流程爲:將求和對象work傳入線程,線程執行過程當中三個隨機數的產生觸發了求和事件,經過傳遞進來的work對象調用process()方法,最後將結果返回線程並在控制檯輸出。這種傳遞數據的方式是在上面兩種傳參的基礎上進行了薄層封裝,並無直接將參數傳遞給線程,而是經過傳遞的對象進行邏輯處理以後將結果返回。this
先來看對於未檢查異常在run()方法中是如何處理的。spa
運行結果:
由上能夠看出對於未檢查異常,從線程將會直接宕掉,主線程繼續運行。那麼在主線程中能不能捕獲到異常呢?咱們直接將所有代碼塊try catch起來
而後你會發現並無什麼卵用,主線程沒有捕獲到任何異常信息,和未檢出異常一模一樣,從線程直接宕掉,主線程繼續運行
若是先檢查異常呢?
IDEA提示:
結果仍是讓人失望,程序還沒運行,IDEA已經提示報錯,原來run()方法自己不支持拋出異常的,方法重寫更不容許throws,因此run()方法不支持往外拋出異常。
最後再試下能不能在run()方法中try catch捕獲異常,
運行結果:
原來在run()方法中try catch是能捕捉到異常的。因此對於多線程已檢查的異常咱們能夠經過try catch進行處理,而對於未檢查的異常,若是沒有處理,一旦拋出該線程立馬宕掉,主線程則繼續運行,那麼未檢查的異常也所有須要try catch嗎?固然這也是一種方式。除此以外,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()方法,再來看它的源碼:
1 public void uncaughtException(Thread t, Throwable e) { 2 //是否存在父線程組 3 if (parent != null) { 4 parent.uncaughtException(t, e); 5 } else { 6 //獲取默認異常處理器 7 Thread.UncaughtExceptionHandler ueh = 8 Thread.getDefaultUncaughtExceptionHandler(); 9 //若是非空,調用其uncaughtException方法 10 if (ueh != null) { 11 ueh.uncaughtException(t, e); 12 //不然打出標準錯誤信息 13 } else if (!(e instanceof ThreadDeath)) { 14 System.err.print("Exception in thread \"" 15 + t.getName() + "\" "); 16 e.printStackTrace(System.err); 17 } 18 } 19 }
若是已經設置異常處理器,那麼直接返回,若是沒有設置,返回當前線程組,而且調用線程組的uncaughtException方法時(如上圖),若是該線程組重寫了uncaughtException方法,直接調用;若是沒有,調用該線程組的父線程組;若是父線程組仍然沒有重寫,調用爺爺線程組,以此類推。可是若是全部的線程組都沒有重寫,進入else裏面,在else中獲取默認處理器,若是默認有,執行uncaughtException方法,若是沒有直接system.err。
以上就是異常處理器的處理邏輯,能夠看出uncaughtExceptionHandler處理器優先級要高於defaultUncaughtException,這也符合就近原則,若是自身擁有了,又何須調用全局擁有的呢!
1 public class TestThread { 2 3 public static void main(String[] args) { 4 MyThread myThread = new MyThread(); 5 myThread.setUncaughtExceptionHandler((Thread t,Throwable e)->{ 6 System.out.println("出現異常..."); 7 System.out.println("線程名稱: "+myThread.getName()); 8 System.out.println("異常信息: "+e.getMessage()); 9 }); 10 myThread.start(); 11 System.out.println("main runing...."); 12 System.out.println("main runing...."); 13 System.out.println("main runing...."); 14 System.out.println("main runing...."); 15 } 16 } 17 class MyThread extends Thread { 18 public void run() { 19 //拋出異常 20 int i = 13 / 0; 21 } 22 }
運行結果:
以上示例能夠看出,儘管爲檢查異常,經過異常處理器依然可以感知異常和信息獲取,不會直接宕掉了。主要注意的是,必須在調用start()方法以前設置異常處理器,不然線程依舊直接宕掉。
Java多線程異常處理機制依賴於Thread內部兩個異常處理器,uncaughtExceptionHandler和defaultUncaughtExceptionHandler。二者之間爲個體和全局之間的關係,若是前者已經設置,那麼直接使用,不然使用後者。大體步驟爲:
a. 若是設置了異常處理器uncaughtExceptionHandler直接使用。
b. 若是沒設置,將會在祖先線程組中查找第一個重寫了uncaughtException的線程組,而後調用他的uncaughtException方法
c. 若是都沒有重寫,那麼使用應用默認的全局異常處理器defaultUncaughtExceptionHandler
d. 若是仍是沒有設置,直接標準錯誤打印信息
若是想要設置線程特有的異常處理器,能夠調用set方法進行設置;若是想要對全局進行設置,能夠調用靜態方法進行設置,須要注意的是必需要在調用start()方法以前設置。