線程池使用不合理搞崩系統你見過嗎?看我這篇就夠了!
核心線程數過多:
如果核心線程數量設置得過高,可能會導致CPU過度調度,增加上下文切換的開銷,甚至耗盡系統可用的線程資源。
最大線程數過大:
當任務隊列滿載且所有工作線程都在運行時,如果最大線程數設置得過高,可能會導致大量線程等待任務,浪費資源,甚至引發內存溢出。
線程池允許創建的最大線程數,當任務到達時,如果當前線程數小于最大線程數,即使核心線程都是空閑的,也會創建新的線程來處理任務。
當線程池的最大線程數設置得過大時,可能會對系統產生以下幾方面的影響:
資源耗盡:
內存消耗增加:每個線程都會占用一定的堆棧空間,
線程數過多可能導致內存消耗過大,
甚至引發OutOfMemoryError。
CPU資源過度分配:
過多的線程會導致CPU過度調度,
增加上下文切換的頻率,
降低CPU的利用率和整體系統的吞吐量。
性能下降:
上下文切換開銷:
頻繁的上下文切換會消耗大量的CPU時間,
降低線程的實際執行效率。
I/O等待:
如果線程執行的任務涉及I/O操作,
過多的線程在等待I/O完成時會占用更多的系統資源,
如文件句柄、網絡連接等。
系統穩定性受影響:
死鎖風險增加:
大量線程并發執行時,
資源競爭更加激烈,
增加了死鎖的風險。
系統響應時間變長:
過多的線程導致系統調度負擔加重,
響應用戶請求的時間可能會顯著增加。
管理復雜度提升:
監控和調試難度加大:
線程數過多使得跟蹤和分析線程狀態變得更加困難,
增加了問題定位和故障排查的復雜度。
任務隊列設置不當:
隊列容量過大或無界:如果任務隊列的容量設置得過大或者沒有限制,當系統負載高時,可能會導致內存消耗過大,甚至發生OutOfMemoryError。
頻繁的創建和銷毀線程,畢竟線程是較重的資源,頻繁的創建和銷毀對系統性能是沒好處的。
隊列容量過小:
如果隊列容量太小,可能會導致任務被拒絕執行,從而影響系統的正常運行。
任務執行時間過長:
如果提交到線程池的任務執行時間過長,而線程池的核心線程數又相對較少,可能會導致線程池中的所有線程都被長時間占用,無法處理新的任務請求,造成系統響應延遲或拒絕服務。
資源競爭 導致 常見的并發問題:
當多個線程同時訪問共享資源時,如果沒有正確的同步機制,可能會引發死鎖或數據不一致的問題,導致系統不穩定。
資源爭用(Resource Contention):
資源爭用是指多個線程同時競爭有限的系統資源,導致性能下降。當多個線程同時請求同一個資源時,可能會出現資源爭用問題。
//多個讀取線程 和 一個寫入線程 同時訪問 共享的列表list。
//由于讀取和寫入都需要獲取列表的鎖,
//可能會導致讀取線程和寫入線程之間的資源爭用,
//從而降低性能。
class YYExample {
private static List<Integer> list = new ArrayList<>();
public static void main(String[] args) {
Runnable reader = () -> {
while (true) {
synchronized (list) {
for (Integer value : list) {
// 讀取共享列表
System.out.println(value);
}
}
}
};
Runnable writer = () -> {
while (true) {
synchronized (list) {
// 寫入共享列表
list.add(1);
}
}
};
// 創建多個讀取線程和寫入線程
for (int i = 0; i < 50; i++) {
new Thread(reader).start();
}
new Thread(writer).start();
}
}
死鎖
`thread1` 先獲取 `resource1`,然后嘗試獲取 `resource2`,
而 `thread2` 先獲取 `resource2`,然后嘗試獲取 `resource1`。
如果這兩個線程同時運行,它們可能會相互等待對方釋放資源,導致死鎖。
class DemoExample {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1 acquired resource 1");
synchronized (resource2) {
System.out.println("Thread 1 acquired resource 2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2 acquired resource 2");
synchronized (resource1) {
System.out.println("Thread 2 acquired resource 1");
}
}
});
thread1.start();
thread2.start();
}
}
數據不一致
class Counter {
private int count;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
在沒有同步機制的情況下,
多個線程并發執行 `increment()` 方法可能導致競態條件,
從而導致最終的計數結果不正確。
異常處理不當:
如果線程在執行任務時拋出未捕獲的異常,
且沒有適當的異常處理機制,
可能會導致線程池中的線程停止工作,
減少可用線程的數量,
影響系統性能。
當線程在執行任務時拋出異常,正確的捕獲和處理異常是保證程序穩定性和健壯性的關鍵。以下是在Java中如何捕獲和處理線程中拋出的異常的幾種方法:
1. 在Runnable或Callable接口實現中捕獲異常
如果你的任務是通過實現Runnable或Callable接口來定義的,你可以在實現的方法中直接捕獲異常。例如:
public class Task implements Runnable {
@Override
public void run() {
try {
// 執行可能拋出異常的任務
doSomething();
} catch (Exception e) {
// 異常處理邏輯,如記錄日志、發送警報等
System.err.println("Task failed due to: " + e.getMessage());
e.printStackTrace();
}
}
private void doSomething() throws Exception {
// 可能拋出異常的代碼
}
}
2. 使用Future獲取異常
當使用Callable接口并配合ExecutorService時,可以通過Future.get()方法獲取任務的結果或拋出的異常:
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(new Task());
try {
future.get(); // 這里會拋出任務中拋出的異常
} catch (ExecutionException e) {
// 獲取并處理原始異常
Throwable cause = e.getCause();
System.err.println("Task failed due to: " + cause.getMessage());
} catch (InterruptedException e) {
// 處理中斷異常
}
3. 設置UncaughtExceptionHandler
你可以為線程設置一個UncaughtExceptionHandler,當線程拋出未捕獲的異常時,這個處理器會被調用來處理異常:
Thread thread = new Thread(new Task());
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
// 異常處理邏輯
System.err.println(t.getName() + " throws an exception: " + e.getMessage());
}
});
thread.start();
4. 監聽線程池異常
對于ExecutorService,可以監聽其內部線程的異常。這通常需要自定義線程工廠并設置UncaughtExceptionHandler:
ThreadFactory factory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((t, e) -> {
System.err.println(t.getName() + " throws an exception: " + e.getMessage());
})
.build();
ExecutorService executor = Executors.newFixedThreadPool(10, factory);
結論:
為了避免這些問題,合理配置線程池參數,監控線程池的狀態,以及對任務進行適當的異常處理和資源管理是非常重要的。