Wetts's blog

Stay Hungry, Stay Foolish.

0%

Java多线程编程实战指南-第11章-Serial Thread Confinement(串行线程封闭)模式

读书笔记 - Java 多线程编程实战指南


第十一章 Serial Thread Confinement(串行线程封闭)模式

模式介绍

  • 如果并发任务的执行涉及某个非线程安全对象,而我们又希望因此而引入锁,那么我们可以考虑使用 Serial Thread Confinement 模式。
  • Serial Thread Confinement 模式的核心思想是通过将多个并发的任务存入队列实现任务的串行化,并为这些串行化的任务创建唯一的一个工作者线程进行处理。因此,这个唯一的工作者线程所访问的非线程安全对象由于只有一个线程访问它,对其的访问自然无需加锁,从而避免了锁的开销及由锁可能引发的问题。
  • 当然,如果我们对并发任务访问的非线程安全对象进行加锁,也能实现任务的串行化从而实现线程安全,另外 Serial Thread Confinement 模式串行化并发任务所使用的队列本身也会涉及锁。因此,Serial Thread Confinement 模式的本质是使用一个开销更小的锁(串行化并发任务时所用队列涉及的锁)去替代另一个可能的开销更大的锁(为保障并发任务所访问的非线程安全对象可能引入的锁)。

-

模式架构

-

实战案例

某系统需要支持从内网的某台 FTP 服务器上下载一批文件的功能。该功能的实现会用到一款开源 FTP 客户端组件。该 FTP 客户端组件并非线程安全,因此如果多个线程共享其实例可能引起数据错乱。另外,系统中会有多个线程需要从服务器上下载文件,并且我们不希望这些需要下载文件的线程等待要下载的文件下载完毕后才能进行其他处理。因此,这里我们需要使用异步编程。如果我们采用多个线程去并发下载一批文件,并使其中的每个线程都持有一个特有的 FTP 客户端实例(即使用 Thread Specific Storage 模式)。那么,线程安全是得到保障了。但这样意味着某一个时刻该系统与对端 FTP 服务器建立了多个网络连接。而这种情况除非必要的时候,否则我们不希望其出现。当然,使用锁也能够实现 FTP 客户端的线程安全,但是,这样一来从服务器上下载文件其实是串行的(逐一下载文件)。并且,文件下载过程中涉及的网络 I/O、文件 I/O 都会引起上下文切换,为保障 FTP 客户端线程安全而引入的锁增加了额外的上下文切换,从而增加系统的负担。

本案例 Demo 详见 multithreading 工程的 com.SerialThreadConfinement 包

-

Serial Thread Confinement 模式的评价与实现考量

  • Serial Thread Confinement 模式的典型应用场景包括一下两个:
  • 需要使用非线程安全对象,但又不希望引入锁: 任务的执行涉及非线程安全对象,如果采用锁去保证对这些对象访问的线程安全,这些锁的开销比起将任务通过队列中转涉及锁的开销更大的话,那么我们可以使用 Serial Thread Confinement 模式。
  • 任务的执行涉及 I/O 操作,但我们不希望过多的 I/O 线程增加上下文切换: I/O 操作,如文件 I/O、网络 I/O 会增加系统的上下文切换。因此,如果涉及 I/O 的线程越多,那么系统的处理效率可能反而越低。这是由于过多的上下文切换消耗了过多的系统资源。

-

Java 标准库实例

Swing 中的实用工具类 SwingUtilities 就使用了 Serial Thread Confinement 模式。该类的 invokeLater 方法使得多个应用线程能够并发提交与 Swing GUI 有关的任务。而这些任务仅由唯一的一个线程(即 Swing 的 Event Loop 线程)去负责执行。从而保障了 Swing GUI 层的线程安全。这里,SwingUtilities 类相当于 Serializer 参与者,其 invokeLater 方法相当于 Serializer 参与者的 service 方法。而 Swing Event Loop 线程则相当于 WorkerThread 参与者。