🤜 示例
例如,海底撈的服務員(線程),輪流處理每位客人的點餐(任務),如果為每位客人都配一名專屬的服務員,那
么成本就太高了(對比另一種多線程設計模式:Thread-Per-Message)
注意,不同任務類型應該使用不同的線程池,這樣能夠避免飢餓,並能提升效率
例如,如果一個餐館的工人既要招呼客人(任務類型A),又要到后廚做菜(任務類型B)顯然效率不咋地,分成
服務員(線程池A)與廚師(線程池B)更為合理,當然你能想到更細致的分工
演示:固定大小線程池會有飢餓現象
兩個工人是同一個線程池中的兩個線程
他們要做的事情是:為客人點餐和到后廚做菜,這是兩個階段的工作
客人點餐:必須先點完餐,等菜做好,上菜,在此期間處理點餐的工人必須等待
后廚做菜:沒啥說的,做就是了
比如工人A 處理了點餐任務,接下來它要等着 工人B 把菜做好,然后上菜,他倆也配合的蠻好
但現在同時來了兩個客人,這個時候工人A 和工人B 都去處理點餐了,這時沒人做飯了,導致飢餓
@Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {
static final List<String> MENU = Arrays.asList("地三鮮", "宮保雞丁", "辣子雞丁", "烤雞翅");
static Random RANDOM = new Random();
// 隨機返回一個菜
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
//固定大小為2的線程池
ExecutorService waiterPool = Executors.newFixedThreadPool(2);
waiterPool.execute(() -> {
log.debug("處理點餐...");
//具體的做菜交與另一線程去做
Future<String> f = waiterPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
log.debug("處理點餐...");
Future<String> f = waiterPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
輸出
18:30:48.374 c.TestDeadLock [pool-1-thread-2] - 處理點餐...
18:30:48.374 c.TestDeadLock [pool-1-thread-1] - 處理點餐...
發生了飢餓,這不叫死鎖,如果使用jstack,這與死鎖的定義也不相同。
👨🚒 解決方法
可以增加線程池的大小,不過不是根本解決方案,還是前面提到的,不同的任務類型,采用不同的線程
池,例如:
@Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {
static final List<String> MENU = Arrays.asList("地三鮮", "宮保雞丁", "辣子雞丁", "烤雞翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
//兩個線程池
ExecutorService waiterPool = Executors.newFixedThreadPool(1);
ExecutorService cookPool = Executors.newFixedThreadPool(1);
waiterPool.execute(() -> {
log.debug("處理點餐...");
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
log.debug("處理點餐...");
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
輸出:
18:31:52.854 c.TestDeadLock [pool-1-thread-1] - 處理點餐...
18:31:52.858 c.TestDeadLock [pool-2-thread-1] - 做菜
18:31:52.858 c.TestDeadLock [pool-1-thread-1] - 上菜: 辣子雞丁
18:31:52.859 c.TestDeadLock [pool-1-thread-1] - 處理點餐...
18:31:52.860 c.TestDeadLock [pool-2-thread-1] - 做菜
18:31:52.860 c.TestDeadLock [pool-1-thread-1] - 上菜: 地三鮮
可以發現,現在正常了。
小結:
當線程池任務間發生了依賴的時候,就應該考慮線程池飢餓的問題。
線程池中的任務應是同類的、獨立的。