读书笔记 - Java 多线程编程实战指南
第十四章 Half-sync/Half-async(半同步/半异步)模式
模式介绍
- Half-sync/Half-async 模式集成了同步编程和异步编程的优势,它通过同步任务和异步任务的共同协作来完成一个计算,即保持了同步编程的简单性,又充分发挥异步编程在提高系统并发性方面的优势。该模式就如一个称职的管理者,知晓不同下属各自的优势和弱点,在指派任务的时候能够根据下属的优势和弱点扬长避短,并使各个下属相互协作,共同完成工作。
-
模式架构
- Half-sync/Half-async 模式是一个分层架构。它包含 3 个层次:异步任务层、同步任务层和队列层。Half-sync/Half-async 模式的核心是如何将系统中的任务进行恰当的分解,使各个子任务落入适合的层次中。
- 低级的任务(如与用户界面有关的任务)或者耗时较短的任务可以安排在异步任务层。而高级的任务(如数据库访问)或者耗时较长的任务可以安排在同步任务层。而异步任务层和同步任务层这两层之间的协作通过队列层进行解藕(Decoupling):队列层负责异步任务和同步任务层之间的数据交换。
-
实战案例
某系统的告警功能被封装在一个模块中。告警模块的主要功能是将其接收到的告警信息发送到告警服务器上。该模块的入口类是 AlarmMgr。其他模块(业务模块)需要发送告警信息时只需调用 AlarmMgr 的 sendAlarm 方法即可。告警模块中的类 AlarmAgent 负责与告警服务器对接,它通过网络连接将告警信息上报到告警服务器。从业务模块的角度来看,告警模块需要解决如下两个问题。
- 告警信息最终是要通过网络连接发送到告警服务器上的。 而网络 I/O 是个相对慢的操作,我们不希望业务模块在调用 AlarmMgr.sendAlarm 时受此影响。也就是说,业务模块对 AlarmMgr.sendAlarm 的调用应该尽可能快地返回,这样不影响业务模块的处理能力。
- 当某个告警产生的条件(通常时故障)持续存在时,相应的告警信息会反复地被发送到告警模块。 业务模块需要在某条告警信息被重复发送到告警模块达到指定次数后,不再在其日志中打印详细的告警信息,而只是打印一个该要信息。因此,AlarmMgr 的 sendAlarm 方法需要返回要发送的告警信息当前被重复的次数。
上述问题涉及两个操作,前者时想对慢的操作(网络 I/O),后者是个相对快的操作(计数)。因此,这里我们可以应用 Half-sync/Half-async 模式:AlarmMgr 可以看成 AsyncTask 参与者实例。其 sendAlarm 方法相对于 dispatch 方法。该方法先对告警信息进行初步处理-计算该告警信息被重复发送到告警模块的次数,再将告警信息放入队列,由同步层的任务(告警发送线程)将告警信息上报至告警服务器。这样,sendAlarm 方法是在业务模块线程中执行的,其消耗的时间主要就在计算告警信息被重复发送的次数以及告警信息入队列。而真正将告警信息通过网络连接上报到服务器的操作是由同步层中的告警发送线程(工作者线程)执行的。因此,这就产生了业务线程和告警发送线程并发执行各自的操作,后者执行的快慢不会影响前者的执行。
本案例 Demo 详见 multithreading 工程的 com.Half_sync_Half_async 包
-
Half-sync/Half-async 模式的评价与现实考量
-
Java 标准库实例
Java Swing 为了避免其 GUI 组件(如按钮)出现死锁,而特意采用单线程模型。所有的 GUI 组件都是运行在唯一的一个线程,即 Event Loop 线程中的。在 Event Loop 线程中运行耗时较长的任务会导致用户界面被“冻住”而无法响应用户操作。为了解决这个问题,JDK 1.6 引入了 SwingWorker 类。该类应用了 Half-sync/Half-async 模式。SwingWorker 使得一些耗时较短的任务运行在 Event Loop 线程中,而耗时较长的任务在其维护的后台线程中。因此,SwingWorker 相当于 Half-sync/Half-async 模式的 AsyncTask 参与者实例。其 doInBackground 方法相当于 AsyncTask 参与者的 dispatch 方法。而 SwingWorker 内部维护的后台线程则相当于 SyncTask 参与者实例,它们负责真正执行耗时较长的任务。