线程池饥饿问题
🤜 示例
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那
么成本就太高了(对比另一种多线程设计模式: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] - 上菜: 地三鲜
可以发现,现在正常了。
小结:
当线程池任务间发生了依赖的时候,就应该考虑线程池饥饿的问题。
线程池中的任务应是同类的、独立的。
你所看得到的天才不过是在你看不到的时候还在努力罢了!