1.線程組
1.1創建線程組的方法
public class xianchengzu {public static void main(String[] args) {ThreadGroup group = new ThreadGroup("group");// 創建線程組時指定父線程組ThreadGroup parent = new ThreadGroup("parent");ThreadGroup child = new ThreadGroup(parent, "child");// 向線程組中添加線程Thread thread1 = new Thread(group,"thread1"){public void run(){System.out.println("我是" + group.getName() + "線程組的一個線程,名稱為:"+ this.getName());}};thread1.start();// 通過runnable創建線程Thread thread2 = new Thread(group,new run());thread2.start();}
}class run implements Runnable{public void run(){System.out.println("我是實現runnable接口的線程");}
}
1.2線程組常用方法
// 獲取線程組名稱
String name = group.getName();// 獲取線程組中活躍線程的估計數
int activeCount = group.activeCount();// 獲取線程組中活躍子線程組的估計數
int activeGroupCount = group.activeGroupCount();// 中斷線程組中所有線程
group.interrupt();// 設置線程組中所有線程的優先級
group.setMaxPriority(Thread.NORM_PRIORITY);// 枚舉線程組中的線程
Thread[] threads = new Thread[group.activeCount()];
group.enumerate(threads); // 將線程組中的線程復制到數組中
2.線程池
線程池是一個包含了能提供相同功能的多個線程的集合。
2.1ThreadPoolExecutor
ava 線程池的核心實現是ThreadPoolExecutor
,其構造方法包含以下核心參數:
public ThreadPoolExecutor(int corePoolSize, // 核心線程數int maximumPoolSize, // 最大線程數long keepAliveTime, // 非核心線程空閑超時時間TimeUnit unit, // 超時時間單位BlockingQueue<Runnable> workQueue,// 任務阻塞隊列ThreadFactory threadFactory, // 線程工廠RejectedExecutionHandler handler // 拒絕策略
)
各參數的含義:
- corePoolSize:線程池長期保持的線程數,即使線程空閑也不會銷毀
- maximumPoolSize:線程池允許創建的最大線程數
- keepAliveTime:當線程數超過核心線程數時,多余線程的空閑存活時間
- workQueue:用于存放等待執行的任務的阻塞隊列
- threadFactory:用于創建新線程的工廠
- handler:當任務無法被處理時的拒絕策略
public class xianchengchi {public static void main(String[] args) {ThreadPoolExecutor pool01 = new ThreadPoolExecutor(3,5,500,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));for(int i=0;i<10;++i){DoWork doWork = new DoWork(i);pool01.execute(doWork);System.out.println("我是線程池,當前池中的線程數為:"+pool01.getActiveCount()+ " 線程池的總容量為:"+pool01.getPoolSize());}for(int i=0;i<8;++i){System.out.println("我是01線程池,目前池內線程總數為:"+ pool01.getPoolSize() + " 等待隊列中的任務為:"+pool01.getQueue().size());try{Thread.currentThread().sleep(100);}catch (InterruptedException e){e.printStackTrace();}}pool01.shutdown();}
}class DoWork implements Runnable {private int i;public DoWork(int i) {this.i = i;}@Overridepublic void run() {System.out.println("我是" + i + "號任務,我的任務執行完了");try{Thread.currentThread().sleep(100);}catch (InterruptedException e){e.printStackTrace();}}
}
上面代碼創建了一個線程池pool01,核心線程數為3,最大線程數為5,空閑存活時間為500毫秒,等待隊列為5。
為線程池添加10個線程,最初,線程池的核心線程數從0到3,此時新加入的任務會被放入等待隊列中,如果等待隊列也滿了,才會繼續增加線程池的線程數,如果線程池達到最大的線程數時繼續加入線程,則會調用拒絕策略。
下面是一次運行的結果。
我是0號任務,我的任務執行完了
我是線程池,當前池中的線程數為:1 線程池的總容量為:1
我是線程池,當前池中的線程數為:2 線程池的總容量為:2
我是線程池,當前池中的線程數為:3 線程池的總容量為:3
我是1號任務,我的任務執行完了
我是線程池,當前池中的線程數為:3 線程池的總容量為:3
我是線程池,當前池中的線程數為:3 線程池的總容量為:3
我是2號任務,我的任務執行完了
我是線程池,當前池中的線程數為:3 線程池的總容量為:3
我是線程池,當前池中的線程數為:3 線程池的總容量為:3
我是線程池,當前池中的線程數為:3 線程池的總容量為:3
我是線程池,當前池中的線程數為:4 線程池的總容量為:4
我是8號任務,我的任務執行完了
我是線程池,當前池中的線程數為:5 線程池的總容量為:5
我是9號任務,我的任務執行完了
我是01線程池,目前池內線程總數為:5 等待隊列中的任務為:5
我是4號任務,我的任務執行完了
我是3號任務,我的任務執行完了
我是5號任務,我的任務執行完了
我是6號任務,我的任務執行完了
我是01線程池,目前池內線程總數為:5 等待隊列中的任務為:1
我是7號任務,我的任務執行完了
我是01線程池,目前池內線程總數為:5 等待隊列中的任務為:0
我是01線程池,目前池內線程總數為:5 等待隊列中的任務為:0
我是01線程池,目前池內線程總數為:5 等待隊列中的任務為:0
我是01線程池,目前池內線程總數為:5 等待隊列中的任務為:0
我是01線程池,目前池內線程總數為:5 等待隊列中的任務為:0
我是01線程池,目前池內線程總數為:3 等待隊列中的任務為:0進程已結束,退出代碼為 0
2.2四種常用方法
除了前面介紹的通過ThreadPoolExecutor構建方法創建的線程池外,還有四種簡化的創建線程池的方法,這幾種方法不需要設置大量的參數,或者當不清楚怎么設置參數時可以參考這些方法。
2.2.1FixedThreadPool(固定大小的線程池)
- 創建方式:通過
Executors.newFixedThreadPool(int nThreads)
方法創建,參數nThreads
指定線程池中線程的數量。 - 核心參數特點:
corePoolSize
和maximumPoolSize
都等于創建時指定的線程數量,即線程池中的線程數量固定不變。keepAliveTime
為 0,因為線程不會被銷毀,始終保持在池中。- 使用
LinkedBlockingQueue
作為任務隊列,容量為Integer.MAX_VALUE
,理論上可以存放無限多的任務。
- 工作原理:當提交任務時,如果有空閑線程,就直接使用空閑線程執行任務;如果沒有空閑線程,任務會被放入任務隊列等待,直到有線程空閑。
- 適用場景:適用于執行長期的、有穩定并發需求的任務,比如服務器端的請求處理,因為固定數量的線程可以保證系統資源的穩定消耗,避免因線程數量波動帶來的性能問題。
2.2.2CachedThreadPool(可緩存的線程池)
- 創建方式:通過
Executors.newCachedThreadPool()
方法創建。 - 核心參數特點:
corePoolSize
為 0,maximumPoolSize
為Integer.MAX_VALUE
,即線程池中的線程數量可以根據任務的多少動態調整。keepAliveTime
為 60L,單位是TimeUnit.SECONDS
,即非核心線程如果閑置 60 秒,就會被銷毀。- 使用
SynchronousQueue
作為任務隊列,這個隊列不存儲任務,每個插入操作必須等待另一個線程的移除操作。
- 工作原理:當提交任務時,如果有空閑線程,就直接使用空閑線程執行任務;如果沒有空閑線程,就創建新的線程來執行任務。當線程空閑時間超過 60 秒,就會被銷毀,所以線程池中的線程數量會根據任務的提交情況動態增減。
- 適用場景:適用于執行大量的短期異步任務,比如異步日志記錄、異步數據處理等場景。由于它可以快速創建和銷毀線程,能很好地應對突發的大量任務請求。
2.2.3SingleThreadExecutor(單線程的線程池)
- 創建方式:通過
Executors.newSingleThreadExecutor()
方法創建。 - 核心參數特點:
corePoolSize
和maximumPoolSize
都為 1,即線程池中始終只有一個線程。keepAliveTime
為 0,線程不會被銷毀。- 使用
LinkedBlockingQueue
作為任務隊列,容量為Integer.MAX_VALUE
。
- 工作原理:所有提交的任務都會按照順序依次由這個唯一的線程來執行,前一個任務執行完后,才會執行下一個任務,保證了任務的串行執行。
- 適用場景:適用于需要保證任務順序執行,或者任務之間有依賴關系的場景,比如數據庫的單線程操作,確保數據操作的一致性和順序性 。
2.2.4ScheduledThreadPool(支持定時及周期性任務執行的線程池)
- 創建方式:通過
Executors.newScheduledThreadPool(int corePoolSize)
方法創建,參數corePoolSize
指定線程池中的核心線程數量。 - 核心參數特點:
corePoolSize
是創建時指定的核心線程數量,maximumPoolSize
為Integer.MAX_VALUE
。keepAliveTime
為 0,核心線程不會被銷毀。- 使用
DelayedWorkQueue
作為任務隊列,用于存儲延遲執行的任務。
- 工作原理:除了具備普通線程池提交任務執行的功能外,還支持定時執行任務(如延遲一定時間后執行)和周期性執行任務(如每隔固定時間執行一次)。
- 適用場景:適用于需要定時執行或者周期性執行的任務,比如定時備份數據、定時發送郵件、周期性的系統狀態檢查等場景。
3.線程的異常處理
使用Thread類的線程或是實現了Runnable接口的線程需要在 run 方法中使用try-catch捕獲錯誤,在主程序中捕獲會失效,但是實現callable接口的線程可以在主程序中捕獲錯誤。
原因是在Thread類中有一個接口 UncaughtExceptionHandler,它包含一個uncaughtException方法,該方法原意是專門對原本線程中沒有捕獲成功的異常進行最終捕獲處理,同時,當線程未捕獲異常而進入該方法時,所有拋出的異常都會被java虛擬機所忽略,即不再對外拋出。
由于java的Thread類默認使用uncaughtException進行空處理,而java虛擬機又會忽略該方法之后的拋出異常,因此我們經常看到的結果是內部線程發生異常時,在外層線程看來,是既不能成功對內部線程的異常進行catch,也不能獲取其詳細信息。
根本原因是:子線程和主線程是完全獨立的執行流(擁有各自的調用棧),子線程中拋出的異常無法直接 “滲透” 到主線程的調用棧中。
具體來說:????????
- 當我們通過
thread.start()
啟動子線程時,子線程的run()
方法會在一個全新的調用棧中執行(與主線程的調用棧完全分離)。 - 如果
run()
方法中拋出未捕獲的異常(比如RuntimeException
),這個異常只會在子線程的調用棧中傳播,不會影響主線程的執行流程。 - 主線程的
try-catch
只能捕獲主線程自身調用棧中產生的異常,無法 “跨線程” 捕獲子線程的異常。
為什么 Callable 的異常可以在主程序捕獲?
Callable
與Runnable
的核心區別在于:Callable
的異常會被線程池 “封裝并保存”,通過Future
對象傳遞給主線程。
具體流程:
Callable
的call()
方法允許聲明拋出異常(包括受檢異常),當它拋出異常時,線程池會捕獲這個異常,并將其封裝到Future
對象中。- 主線程調用
future.get()
獲取結果時,如果Callable
執行中拋出了異常,get()
方法會將封裝的異常以ExecutionException
的形式拋出(ExecutionException
的getCause()
方法可以獲取原始異常)。 - 因此,主線程可以通過
try-catch
捕獲ExecutionException
,從而間接獲取Callable
中的異常。
1. 使用Runnable
接口的線程異常處理示例
public class RunnableExceptionExample {public static void main(String[] args) {Runnable runnable = () -> {// 模擬拋出異常throw new RuntimeException("Runnable中的異常");};Thread thread = new Thread(runnable);try {// 這里直接在main方法中捕獲,捕獲不到異常thread.start(); } catch (Exception e) {System.out.println("在main方法中捕獲Runnable異常: " + e.getMessage());}}
}
上述代碼中,在main
方法里直接捕獲Runnable
線程執行時拋出的異常是捕獲不到的。如果要處理Runnable
線程中的異常,需要在run
方法內部進行捕獲,修改代碼如下:
public class RunnableExceptionHandleInRunExample {public static void main(String[] args) {Runnable runnable = () -> {try {// 模擬拋出異常throw new RuntimeException("Runnable中的異常"); } catch (Exception e) {System.out.println("在Runnable的run方法中捕獲異常: " + e.getMessage());}};Thread thread = new Thread(runnable);thread.start(); }
}
2. 使用Callable
接口的線程異常處理示例
public class FutureCatch {public static void main(String[] args) throws InterruptedException, ExecutionException {Callable<String> stringCallable = new subCallable();FutureTask<String> futureTask = new FutureTask<String>(stringCallable);Thread thread = new Thread(futureTask);thread.start();try{System.out.println(futureTask.get());}catch(Exception e){System.out.println("返回字符串失敗了,下標有問題");}}
}class subCallable implements Callable<String> {public String call() throws Exception{String str = "我是測試字符串";String subStr = str.substring(6,10);return subStr;}
}
4.多線程的安全關閉
多線程的關閉設計其他線程的運行和穩定,所以一個線程并不是調用一個關閉的方法就能馬上停止的,要考慮線程使用的資源是否已經釋放、線程是否需要在退出服務前通知其它線程或進行一些準備工作。
public class CloseThread {public static void main(String[] args) throws InterruptedException, ExecutionException {Thread clost = new Thread(new waittoclose());clost.start();try{// 讓主線程休眠Thread.sleep(2000);}catch (InterruptedException e){e.printStackTrace();}// 調用方法后isInterrupted() 變為true,并且拋出InterruptedExceptionclost.interrupt();}
}class waittoclose implements Runnable{@Overridepublic void run() {while(Thread.currentThread().isInterrupted()==false){try{System.out.println(Thread.currentThread().getName()+"正在運行");Thread.sleep(200);}catch(InterruptedException e){System.out.println("資源正在釋放");System.out.println("線程關閉中");Thread.currentThread().interrupt();}}}
}
當線程在執行Thread.sleep()
?時,如果收到中斷信號(其他線程調用了它的?interrupt()
?方法),會發生兩件事:
sleep()
?會立即拋出?InterruptedException
?異常,提前結束休眠- 自動清除線程的中斷狀態(將中斷標記設為?
false
)
在Runnable接口中,try模塊的Thread.sleep(200)在察覺到interrupt后,會按照上述拋出異常和清除中斷狀態,之后catch模板捕獲異常,并進行善后操作,最后還要關閉一次線程,這樣線程才能實現安全關閉。
5.自定義多線程異常處理
這里給一個簡單的自定義異常:
public class ExceptionHandle implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println(t.getName() + "拋出了異常信息:" + e.toString());}
}
- 實現了
Thread.UncaughtExceptionHandler
接口 - 當線程拋出未捕獲異常時,JVM會自動調用此方法
public class CatchExceptionFactory implements ThreadFactory {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setUncaughtExceptionHandler(new ExceptionHandle()); // 關鍵設置return t;}
}
- 實現了
ThreadFactory
接口 - 為每個新創建的線程設置自定義異常處理器
class divide implements Runnable {@Overridepublic void run() {int a = 30;int b = 3;int step = 1;while(b >= 0) {System.out.println(a + "與" + b + "相除的結果為:");System.out.println(a / b); // 當b=0時這里會拋出ArithmeticExceptionb--;}System.out.println("結束運算");}
}
- 包含一個會引發除零異常的任務
- 當b遞減到0時,
a/b
會拋出ArithmeticException
public class ExceptionTest {public static void main(String[] args) {// 使用自定義線程工廠創建線程池ExecutorService executorService = Executors.newCachedThreadPool(new CatchExceptionFactory());// 提交任務executorService.execute(new divide());}
}
運行結果(此時主程序并沒有停止):
30與3相除的結果為:
10
30與2相除的結果為:
15
30與1相除的結果為:
30
30與0相除的結果為:
Thread-0拋出了異常信息:java.lang.ArithmeticException: / by zero