线程池饥饿问题(代码片段)

heliusking heliusking     2023-04-20     521

关键词:

:fist_right: 示例

例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那
么成本就太高了(对比另一种多线程设计模式: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,这与死锁的定义也不相同。

:man_firefighter: 解决方法

可以增加线程池的大小,不过不是根本解决方案,还是前面提到的,不同的任务类型,采用不同的线程
池,例如:

@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] - 上菜: 地三鲜

可以发现,现在正常了。


小结:

当线程池任务间发生了依赖的时候,就应该考虑线程池饥饿的问题。

线程池中的任务应是同类的、独立的。

juc并发编程共享模式之工具threadpoolexecutor多线程设计模式--异步模式之工作线程(定义饥饿&解决饥饿&线程池创建多少线程数目合适)(代码片段)

1.工作线程1.1定义让有限的工作线程(WorkerThread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式例如:海底捞的服务员(线程)... 查看详情

java并发day05threadpoolexecutorjuc(代码片段)

目录ThreadPoolExecutor线程池状态newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutor提交任务关闭线程池饥饿现象创建多少线程池合适任务调度线程池正确处理线程池中的异常定时任务fork/joinJUCAQSReentrantLockReentrantReadWriteLockStampedLockSemaphor... 查看详情

猿创征文|深入理解高并发编程~开篇(代码片段)

目录一、进程与线程二、线程组与线程池1、线程组2、线程组和线程池有啥区别?三、用户线程与守护线程四、并行与并发五、悲观锁与乐观锁1、悲观锁2、乐观锁六、CAS1、什么是CAS?2、CAS带来的问题七、那些年学过的... 查看详情

java并发day05threadpoolexecutorjuc(代码片段)

目录ThreadPoolExecutor线程池状态newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutor提交任务关闭线程池饥饿现象创建多少线程池合适任务调度线程池正确处理线程池中的异常定时任务fork/joinJUCAQSReentrantLockReentrantReadWriteLockStampedLockSemaphor... 查看详情

线程池常见问题总结(代码片段)

线程池一:线程池的概念二:线程池的优点三:常见的线程池四:线程池的配置参数五:线程池的工作流程六:线程池的拒绝策略七:为什么要使用线程池一:线程池的概念线程的创建和销毁都需... 查看详情

线程池常见问题总结(代码片段)

线程池一:线程池的概念二:线程池的优点三:常见的线程池四:线程池的配置参数五:线程池的工作流程六:线程池的拒绝策略七:为什么要使用线程池一:线程池的概念线程的创建和销毁都需... 查看详情

线程池的问题(代码片段)

面试-线程池的成长之路       尹吉欢2018-05-140条评论 27人阅读版权声明:转载请先联系作者并标记出处。java 面试题 背景相信大家在面试过程中遇到面试官问线程的很多,线程过后就是线程池... 查看详情

重写线程池execute方法导致线程池“失效”问题(代码片段)

...了execute方法,通过execute方法来执行任务时打印当前线程,日志显示任务一直在调用者线程里执行(其实并不是),似乎线程池失效了。二、场景复现自定义ThreadPoolTaskExecutor子类importor 查看详情

重写线程池execute方法导致线程池“失效”问题(代码片段)

...了execute方法,通过execute方法来执行任务时打印当前线程,日志显示任务一直在调用者线程里执行(其实并不是),似乎线程池失效了。二、场景复现自定义ThreadPoolTaskExecutor子类importor 查看详情

线程池的堆栈问题(代码片段)

前面的文章已经讲了线程池和线程池的内部实现,这篇文章来了解线程池出错的堆栈信息的打印,毕竟异常堆栈信息的重要性对于程序员来说就像是指南针对于茫茫大海上的船只一样,没有指南针船只只能更加艰难的寻找方向,... 查看详情

线程池(代码片段)

一、简介什么是线程池线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。为什么要用线程池如果并发请求数量很多,但每个线程执行的时间很短,就会出现频繁的创建和销毁线... 查看详情

python令人尴尬的并行问题的进程/线程池(代码片段)

查看详情

12-线程池(代码片段)

线程池定义线程池顾名思义是事先创建若干可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而放回池中,从而减少创建和销毁对象的开销.线程池优点降低资源消耗提高响应速度... 查看详情

java线程池详解(代码片段)

构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情

java线程池详解(代码片段)

构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情

一文简单理解java线程池的问题(代码片段)

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。... 查看详情

每日一博-review线程池(代码片段)

文章目录Pre核心设计与实现运行机制线程池的生命周期ctl解读ctl的相关方法线程池的状态任务执行机制任务调度任务缓冲任务申请任务拒绝Pre线程池(ThreadPool)是一种基于池化思想管理线程的工具.使用线程池可以带来一... 查看详情

多线程(线程池原理)(代码片段)

背景阿里电面-线程池是如何实现的?这个问题的侧重点是什么?普通的数据库连接池的概念一般是这样的,多个被静态代理或者动态代理生成的数据库连接,直接放到一个池子里,比方说一个队列,这个数据库连接池提供了如... 查看详情