多线程分工模式

87 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

一、Thread-Per-Message 模式

Thread-Per-Message 模式——最简单实用的分工方法,Thread-Per-Message 模式就是为每个任务分配一个独立的线程,这是一种最简单的分工方法。

Thread-Per-Message 模式需要注意线程的创建,销毁以及是否会导致OOM。

应用场景:Thread-Per-Message 模式的一个最经典的应用场景是网络编程里服务端的实现,服务端为每个客户端请求创建一个独立的线程,当线程处理完请求后,自动销毁,这是一种最简单的并发处理网络请求的方法。

Thread-Per-Message 模式作为一种最简单的分工方案,Java 中使用会存在性能缺陷。在Java 中的线程是一个重量级的对象,创建成本很高,一方面创建线程比较耗时,另一方面线程占用的内存也比较大。所以为每个请求创建一个新的线程并不适合高并发场景。为了解决这个缺点,Java 并发包里提供了线程池等工具类。

在其他编程语言里,例如 Go 语言,基于轻量级线程实现 Thread-Per-Message 模式就完全没有问题。

对于一些并发度没那么高的异步场景,例如定时任务,采用 Thread-Per-Message 模式是完全没有问题的。

二、Worker Thread 模式

要想有效避免线程的频繁创建、销毁以及 OOM 问题,就不得不提 Java 领域使用最多的Worker Thread 模式。Worker Thread 模式可以类比现实世界里车间的工作模式:车间里的工人,有活儿了,大家一起干,没活儿了就聊聊天等着。Worker Thread 模式中 Worker Thread对应到现实世界里,其实指的就是车间里的工人。

Worker Thread 模式需要注意死锁问题,提交的任务之间不要有依赖性。

应用场景:Worker Thread 模式能避免线程频繁创建、销毁的问题,而且能够限制线程的最大数量。Java 语言里可以直接使用线程池来实现 Worker Thread 模式,线程池是一个非常基础和优秀的工具类,甚至有些大厂的编码规范都不允许用 new Thread() 来创建线程,必须使用线程池。

三、生产者 - 消费者模式

Worker Thread 模式类比的是工厂里车间工人的工作模式。但其实在现实世界,工厂里还有一种流水线的工作模式,类比到编程领域,就是生产者 - 消费者模式。

生产者 - 消费者模式的核心是一个任务队列,生产者线程生产任务,并将任务添加到任务队列中,而消费者线程从任务队列中获取任务并执行。

生产者 - 消费者模式可以直接使用线程池来实现。

image.png

生产者 - 消费者模式的优点

  • 支持异步处理:用户注册后,需要发注册邮件和注册短信。传统的做法有两种

    • 串行方式
    • 并行方式

    image.png

    • 引入消息队列,将不是必须的业务逻辑异步处理

      image.png

  • 解耦:用户下单后,订单系统需要通知库存系统扣减库存。

    image.png

    • 可以消除生产者生产与消费者消费之间速度差异

      image.png

      在计算机当中,创建的线程越多,CPU进行上下文切换的成本就越大,所以我们在编程的时候创建的线程并不是越多越好,而是适量即可,采用生产者和消费者模式就可以很好的支持我们使用适量的线程来完成任务。

      如果在某一段业务高峰期的时间里生产者“生产”任务的速率很快,而消费者“消费”任务速率很慢,由于中间的任务队列的存在,也可以起到缓冲的作用,我们在使用MQ中间件的时候,经常说的削峰填谷也就是这个意思。

      image.png

  • 过饱问题解决方案

    在实际生产项目中会有些极端的情况,导致生产者/消费者模式可能出现过饱的问题。单位时间内,生产者生产的速度大于消费者消费的速度,导致任务不断堆积到阻塞队列中,队列堆满只是时间问题。

    思考:是不是只要保证消费者的消费速度一直比生产者生产速度快就可以解决过饱问题?

    image.png

    我们只要在业务可以容忍的最长响应时间内,把堆积的任务处理完,那就不算过饱。

    什么是业务容忍的最长响应时间?

    比如埋点数据统计前一天的数据生成报表,第二天老板要看的,你前一天的数据第二天还没处理完,那就不行,这样的系统我们就要保证,消费者在24小时内的消费能力要比生产者高才行。

    • 场景一:消费者每天能处理的量比生产者生产的少;如生产者每天1万条,消费者每天只能消费5千条。

      解决办法:消费者加机器,原因:生产者没法限流,因为要一天内处理完,只能消费者加机器。

      image.png

    • 场景二:消费者每天能处理的量比生产者生产的多。系统高峰期生产者速度太快,把队列塞爆了。

      解决办法:适当的加大队列,原因:消费者一天的消费能力已经高于生产者,那说明一天之内肯定能处理完,保证高峰期别把队列塞满就好。

      image.png

    • 场景三:消费者每天能处理的量比生产者生产的多。条件有限或其他原因,队列没法设置特别大。系统高峰期生产者速度太快,把队列塞爆了。

      解决办法:生产者限流,原因:消费者一天的消费能力高于生产者,说明一天内能处理完,队列又太小,那只能限流生产者,让高峰期塞队列的速度慢点。

      image.png