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