Java線程池一:線程基礎
- 2020 年 11 月 29 日
- 筆記
最近精讀Netty源碼,讀到NioEventLoop部分的時候,發現對Java線程&線程池有些概念還有困惑, 所以深入總結一下
線程創建
Java線程創建主要有三種方式:繼承Thread類、實現Runable接口、實現Callable接口
只有通過調用Thread.start()
方法才會真正創建一個線程, 調用Thread.run()
並不會
當調用線程關心任務執行結果時,我們應選擇實現Callable接口的方式創建線程
-
繼承方式實現創建線程
@Test public void testCreate_1() { Thread t = new Thread() { @Override public void run() { System.out.println(Thread.currentThread().getName()); throw new RuntimeException(); } }; t.start(); t.run(); }
-
實現Runnable接口的方式創建線程,這種方式調用線程無法感知任務線程執行結果(是否執行、成功或者異常)
@Test public void testCreate_2() { Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName())); t.start(); }
-
實現Callable接口,調用線程通過FutureTask對象獲取執行結果(返回值或者異常)
@Test public void testCreate_3() throws ExecutionException, InterruptedException { FutureTask<Integer> task = new FutureTask<>(() -> { throw new RuntimeException(); }); new Thread(task).start(); System.out.println(task.get()); }
線程的狀態
我們知道Java線程是使用系統內核線程實現, 所以先來簡單回顧下系統內核線程的狀態
內核線程的狀態
- Ready狀態:當前線程已經就緒,等待系統調度
- Running狀態:當Ready狀態的線程分配到時間片後進入該狀態
- Blocking狀態:運行中的線程因為其他資源未就緒進入該狀態
Java線程的狀態
- NEW: 實例化一個Thread對象後未調用start方法前都是該狀態
- RUNNABLE: JVM裏面的可執行狀態,對應內核線程的Ready或者Running狀態。所以該狀態下線程不一定在在運行,有可能在等待調度
- WAITING: 等待狀態,需要其他線程喚醒後才能重新進入RUNNABLE狀態
- TIMED_WAITING:超時等待,等待一定的時間或者被其他線程喚醒之後可再進入RUNNABLE狀態
- BLOCKED:阻塞狀態,特指等待進入synchronized同步塊的狀態(獲取監視器鎖)
- Termination:終態
只有待獲取監視器鎖時才是阻塞狀態,獲取Java語言實現的鎖(ReentrantLock等)是等待狀態。二者的區別在於監視器鎖的實現依賴內核變量。
異常處理
假設一段業務邏輯沒有考慮運行時異常, 而運行時異常又剛好發生了,那麼對應的線程就會直接崩潰。所以多線程環境下為了讓程序更加健壯穩定, 我們需要捕獲異常。
-
將整個業務邏輯加上異常捕獲(當然代碼就不是很優雅)
@Test public void testExceptionHandle_1() { new Thread(() -> { try { //business code int a = 1, b = 0; a = a / b; } catch (Throwable th) { //log } }).start(); }
-
使用FutureTask異步回掉處理異常(更加優雅,業務邏輯和異常處理邏輯分離)
@Test public void testExceptionHandle_2() { //業務邏輯 FutureTask<Integer> ft = new FutureTask<>(() -> { //business code int a = 1, b = 0; a = a / b; return a; }); Thread t = new Thread(() -> { ft.run(); handleResult(ft); }); t.start(); } //異常處理邏輯 private void handleResult(FutureTask<Integer> ft) { try { System.out.println("the result is " + ft.get()); } catch (InterruptedException e) { //log or ... e.printStackTrace(); } catch (ExecutionException e) { //log or ... e.printStackTrace(); } }
中斷
Java中斷是一種線程間通信手段。比如A線程給B線程發送一個中斷信號,B線程收到這個信號,可以處理也可以不處理。
- 中斷相關的API
void thread.interrupt();//實例方法-中斷線程(線程的中斷標識位置為1)
boolean thread.isInterrupted();//線程是否中斷 & 不清除中斷標識
static boolean Thread.interrupted();//當前線程是否中斷 & 清除中斷標識
-
實例
線程t_1每次循環會判斷當前線程的中斷狀態,如果當前線程已經被中斷(中斷標識位為1)就直接返回;
整個通信過程:主線程把t_1線程的中斷標識位置為1,t_1獲取到中斷標識位為1, 然後結束循環。
@Test public void testInterrupt() throws InterruptedException { Thread t_1 = new Thread(() -> { int i = 0; while (true) { boolean isInterrupt = Thread.interrupted(); if (isInterrupt) { System.out.println("i am interrupt, return"); return; } //business code if (i++ % 10000 == 0) { System.out.println(i); } } }); t_1.start(); for (int i = 0; i < 100000; i++) { ; } t_1.interrupt(); }
其他
-
守護線程
當JVM中的所有的用戶線程都退出後守護線程也會退出
-
優先級
線程的優先級越高,越有可能更快的執行或者獲得更多的可執行時間單元。但是Java線程的優先級只是參考,依賴於具體的實現
-
thread.join()
調用線程進入WAITING狀態,直到thread線程終止
-
thread.yield()
當前線程讓出cpu資源,仍處於RUNNABLE狀態