26 如何使用设计模式优化并发编程?

986 阅读3分钟

大家好,我是小水珠。

在我们使用多线程编程时,很多时候需要根据业务场景设计一套业务功能。其实,在多线程编程中,本身就存在很多成熟的功能设计模式,学好它们,用好它们,那就是如虎添翼了。今天我就带你了解几种并发编程中常用的设计模式。

一 线程上下文设计模式

我们不妨通过一个具体的案例,来看看到底在什么的场景下才需要上下文呢?

在执行一个比较长的请求任务时,这个请求可能会经历很多层的方法调用,假设我们需要将最开始的方法的中间结果传递到末尾的方法中进行计算,一个简单的实现方式就是在每个函数中新增这个中间结果的参数,依次传递下去。代码如下:

微信图片_20220825212154.jpg 微信图片_202208252121541.jpg

执行结果:

微信图片_202208252121542.jpg

除了以上这些方法,其实我们还可以使用ThreadLocal实现上下文。ThreadLocal是线程本地变量,可以实现多线程的数据隔离。ThreadLocal为每一个使用该变量的线程都提供一份独立的副本,线程间的数据是隔离的,每一个线程只能访问各自内部的副本变量。

ThreadLocal中有三个常用的方法:set、get、initialValue,我们可以通过以下一个简单的例子来看看ThreadLocal的使用:

微信图片_202208252121543.jpg

接下来,我们使用ThreadLocal来重新实现最开始的上下文设计。你会发现,我们在两个方法中并没有通过变量来传递上下文,只是通过ThreadLocal获取了当前线程的上下文信息:

微信图片_202208252121544.jpg 微信图片_202208252121545.jpg 微信图片_202208252121546.jpg 微信图片_202208252121547.jpg 运行结果:

微信图片_202208252121548.jpg

二 Thread-Per-Message设计模式

Thread-Per-Message设计模式翻译过来的意思就是每个消息一个线程的意思。例如,我们在处理Socket通信的时候,通常是一个线程处理事件监听以及I/O读写,如果I/O读写操作非常耗时,这个时候便会影响到事件监听处理事件。

这个时候Thread-Per-Message模式就可以很好地解决这个问题,一个线程监听I/O事件,每当监听到一个I/O事件,则交给另一个处理线程执行I/O操作。下面,我们还是通过一个例子来学习下该设计模式的实现。

微信图片_202208252121549.jpg 微信图片_2022082521215410.jpg 微信图片_2022082521215411.jpg 微信图片_2022082521215412.jpg

三 Worker-Thread设计模式

这里的Worker是工人的意思,代表在Worker Thread设计模式中,会有一些工人(线程)不断轮流处理过来的工作,当没有工作时,工人则会处于等待状态,直到有新的工作进来。除了工人角色,Worker Thread设计模式中还包括了流水线和产品。

假设一个物流仓库的物流分拣流水线上有8个机器人,它们不断从流水线上获取包裹并对其进行包装,送其上车。当仓库中的商品被打包好后,会投放到物流分拣流水线上,而不是直接交给机器人,机器人会再从流水线中随机分拣包裹。代码如下:

微信图片_2022082521215413.jpg 微信图片_2022082521215414.jpg 微信图片_2022082521215415.jpg 微信图片_2022082521215416.jpg 微信图片_2022082521215417.jpg

四 总结

平时,如果需要传递或隔离一些线程变量时,我们可以考虑使用上下文设计模式。在数据库读写分离的业务场景中,则经常会用到ThreadLocal实现动态切换数据源操作。但在使用ThreadLocal时,我们需要注意内存泄漏问题,在之前的讲解,我们已经讨论过这个问题了。

当主线程处理每次请求都非常耗时时,就可能出现阻塞问题,这时候我们可以考虑将主线程业务分工到新的业务线程中,从而提高系统的并行处理能力。而 Thread-Per-Message 设计模式以及 Worker-Thread 设计模式则都是通过多线程分工来提高系统并行处理能力的设计模式。