一、定义

让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务,也可以将其归类为工作模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都分配一名专属的服务员,那么成本会很高,对比另一种多线程设计模型(Thread-Per-Message,即为每个消息均涉及一个线程)
注意,不同任务类型应该对应不同的线程池,这样能够避饥饿,并能提升效率
例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率很差,分成服务员(线程池A)与厨师(线程池B)更为合理。


二、饥饿

对于线程池来讲,饥饿现象产生的原因是:线程池大小固定,没有足够的线程来处理新任务

  • 两个工人是同一个线程池中的两个线程
  • 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作

    1. - 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待菜做好<br /> - 后厨做菜:没啥说的,做菜
  • 比如工人A处理了点餐任务,接下来它要等着工人B将菜做好,然后上菜,他俩也配合的蛮好

  • 但现在同时来了两个客人,这个时候工人A和工人B均去处理点餐,但没人做饭,因此陷入饥饿现象。

这里的饥饿并不是死锁,死锁是相互拿着锁但不释放,本例产生的问题的根本原因在于:没有线程去处理做菜任务。

示例代码如下:

  1. import java.util.Arrays;
  2. import java.util.List;
  3. import java.util.Random;
  4. import java.util.concurrent.ExecutionException;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Future;
  8. public class TestStarvation {
  9. static final List<String> MENU = Arrays.asList("地三鲜","宫保鸡丁","辣子鸡");
  10. static Random random = new Random();
  11. static String cooking(){return MENU.get(random.nextInt(MENU.size()));}
  12. public static void main(String[] args) {
  13. //创建size==2的线程池
  14. ExecutorService pool = Executors.newFixedThreadPool(2);
  15. pool.execute(()->{
  16. System.out.println("处理点餐");
  17. Future<String> f= pool.submit(()->{
  18. System.out.println("做菜");
  19. return cooking();
  20. });
  21. try{
  22. System.out.println("上菜");
  23. System.out.println(f.get());
  24. }catch (InterruptedException | ExecutionException e){
  25. e.printStackTrace();
  26. }
  27. });
  28. pool.execute(()->{
  29. System.out.println("处理点餐");
  30. Future<String> f= pool.submit(()->{
  31. System.out.println("做菜");
  32. return cooking();
  33. });
  34. try{
  35. System.out.println("上菜");
  36. System.out.println(f.get());
  37. }catch (InterruptedException | ExecutionException e){
  38. e.printStackTrace();
  39. }
  40. });
  41. }
  42. }

如代码所示,交给线程池的点菜任务,需要等待其它做菜任务的执行结果。如果没有线程去执行做菜任务,那么则会陷入饥饿状态,f.get() 会被阻塞。
当两个线程均分配任务点菜时,因为线程不够导致无线程去处理做菜任务,产生饥饿情况。

饥饿解决方法:

一种饥饿的解决方法是,根据任务类型划分线程池,点菜任务交给waiterPool来处理,做菜任务交给cookingPool来处理。

  1. import java.util.Arrays;
  2. import java.util.List;
  3. import java.util.Random;
  4. import java.util.concurrent.ExecutionException;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Future;
  8. public class TestStarvation {
  9. static final List<String> MENU = Arrays.asList("地三鲜","宫保鸡丁","辣子鸡");
  10. static Random random = new Random();
  11. static String cooking(){return MENU.get(random.nextInt(MENU.size()));}
  12. public static void main(String[] args) {
  13. //创建两个线程池,分别处理不同类型的任务
  14. ExecutorService waiterPool = Executors.newFixedThreadPool(1);
  15. ExecutorService cookingPool = Executors.newFixedThreadPool(1);
  16. waiterPool.execute(()->{
  17. System.out.println("处理点餐");
  18. Future<String> f= cookingPool.submit(()->{
  19. System.out.println("做菜");
  20. return cooking();
  21. });
  22. try{
  23. System.out.println("上菜");
  24. System.out.println(f.get());
  25. }catch (InterruptedException | ExecutionException e){
  26. e.printStackTrace();
  27. }
  28. });
  29. waiterPool.execute(()->{
  30. System.out.println("处理点餐");
  31. Future<String> f= cookingPool.submit(()->{
  32. System.out.println("做菜");
  33. return cooking();
  34. });
  35. try{
  36. System.out.println("上菜");
  37. System.out.println(f.get());
  38. }catch (InterruptedException | ExecutionException e){
  39. e.printStackTrace();
  40. }
  41. });
  42. }
  43. }