一、定义
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务,也可以将其归类为工作模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都分配一名专属的服务员,那么成本会很高,对比另一种多线程设计模型(Thread-Per-Message,即为每个消息均涉及一个线程)
注意,不同任务类型应该对应不同的线程池,这样能够避饥饿,并能提升效率
例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率很差,分成服务员(线程池A)与厨师(线程池B)更为合理。
二、饥饿
对于线程池来讲,饥饿现象产生的原因是:线程池大小固定,没有足够的线程来处理新任务
- 两个工人是同一个线程池中的两个线程
他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
- 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待菜做好<br /> - 后厨做菜:没啥说的,做菜
比如工人A处理了点餐任务,接下来它要等着工人B将菜做好,然后上菜,他俩也配合的蛮好
- 但现在同时来了两个客人,这个时候工人A和工人B均去处理点餐,但没人做饭,因此陷入饥饿现象。
这里的饥饿并不是死锁,死锁是相互拿着锁但不释放,本例产生的问题的根本原因在于:没有线程去处理做菜任务。
示例代码如下:
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
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) {
//创建size==2的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(()->{
System.out.println("处理点餐");
Future<String> f= pool.submit(()->{
System.out.println("做菜");
return cooking();
});
try{
System.out.println("上菜");
System.out.println(f.get());
}catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
});
pool.execute(()->{
System.out.println("处理点餐");
Future<String> f= pool.submit(()->{
System.out.println("做菜");
return cooking();
});
try{
System.out.println("上菜");
System.out.println(f.get());
}catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
});
}
}
如代码所示,交给线程池的点菜任务,需要等待其它做菜任务的执行结果。如果没有线程去执行做菜任务,那么则会陷入饥饿状态,f.get() 会被阻塞。
当两个线程均分配任务点菜时,因为线程不够导致无线程去处理做菜任务,产生饥饿情况。
饥饿解决方法:
一种饥饿的解决方法是,根据任务类型划分线程池,点菜任务交给waiterPool来处理,做菜任务交给cookingPool来处理。
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
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 cookingPool = Executors.newFixedThreadPool(1);
waiterPool.execute(()->{
System.out.println("处理点餐");
Future<String> f= cookingPool.submit(()->{
System.out.println("做菜");
return cooking();
});
try{
System.out.println("上菜");
System.out.println(f.get());
}catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
});
waiterPool.execute(()->{
System.out.println("处理点餐");
Future<String> f= cookingPool.submit(()->{
System.out.println("做菜");
return cooking();
});
try{
System.out.println("上菜");
System.out.println(f.get());
}catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
});
}
}