线程设计模式在按其有助于解决的多线程编程相关的问题可粗略分类如下。
该模式也被称为Immutable (不可变)模式。
多个线程共享一个对象的实例。
当被共享的对象相应的现实世界实体的状态变更时,系统对此要有所反映。但是,通过直接更改该被共享的对象的状态来反映这个变更通常会导致锁的引入以保证线程安全。
将相应现实世界实体建模为状态不可变的对象。当相应实现世界实体的状态变更时,系统通过创建新的对象实例来反映这种状态变更,而不是更改对象本身的状态。
多个线程可以在不使用锁的情况下,以线程安全的方式去访问共享对象。可能导致频繁的对象创建。
Thread Specific Storage模式和Serial Thread Confinement模式也可以在不引入锁的情况下确保线程安全。
该模式也被称为Guarded Waits(受保护等待)模式。
一个方法欲执行的操作(目标动作)所需的前提条件可能暂时无法满足而稍后可能得以满足。
问题
多线程环境中,某个对象的方法被调用时,该方法欲执行的操作所需的状态暂时没有得到满足,而稍后可能得以满足。因此,此时如果该方法返回或者抛出异常,则会迫使客户端代码对其不期望的结果进行处理。
多线程环境中,当某个对象的方法(受保护方法)执行其欲执行的操作所需的状态(保护条件)未被满足时,将当前线程暂挂直到其他线程改变了该对象的状态使得保护条件得以满足时,被暂挂的线程得以唤醒。
Promise模式(第6章)和Producer-Consumer模式实现过程中可能需要使用GuardedSuspension模式。
无
守护线程(Dacmon Thread)不会阻止JVM正常关闭,而用户线程会阻止JVM正常关闭。因此,正常关闭JVM时需要先将用户线程停止。但是,停止一个用户线程时,我们希望该线程能够在其处理完待处理的任务后再行停止。
将线程的停止分为两个阶段:准备阶段和执行阶段。准备阶段主要实现线程停止的标志的设置,执行阶段主要实现线程停止标志的检测并在线程处理完待处理的任务后停止线程。
Producer-Consumer模式(第7章)和Master-Slave模式(第12章)可能需要使用Two-phaseTermination模式以实现其工作者线程的停止。
该模式也被称为Future(期货)模式。
一个对象需要使用另外一个对象的某个方法(以下称为目标方法)的返回值。
目标方法需要消耗较长的处理时间才能返回表示其处理结果的值。在该方法返回之前,客户端代码会被阻塞而无法进行其他处理。
使用异步编程,将目标方法的返回值改为一个凭据对象,而不是表示目标方法真正处理结果的对象(结果对象)。客户端代码通过调用凭据对象的某个方法来获取目标方法的结果对象。在此基础上,采用专门的工作者线程或者线程池去执行目标方法所进行的计算。
目标方法可以看成Factory Method模式"中的工厂方法。
凭据对象用于获取结果对象的方法可能需要等待目标方法对应的计算完成才能返回,这可以使用Guarded Suspension模式(第4章)来实现。Active Object模式可以看成是包含了Promise模式的复合模式,其异步方法的返回值就是一个凭据对象。
无
数据(任务)的提供方的处理能力(速率)与相应的使用方的处理能力(速率)不均衡,或者我们不希望二者的处理能力(速率)相互影响。
数据(任务)的提供方和使用方运行在同一个线程中会导致一方处理能力(速率)的大小对另外一方产生影响,即造成等待。
在数据(任务)的提供方和使用方之间引入一个作为缓冲区的通道,从而使数据(任务)的提供方和使用方可以运行在各自的线程之中。
数据(任务)的提供方和使用方的处理能力相对来说互不影响。
关注点分离(Separation of Concern):数据(任务)的提供方只需要将数据(任务)存入通道即可,它无须关心谁对数据(任务)进行处理;
而数据(任务)的使用方只需要从通道中取出数据(任务)进行处理而无须关心是谁将其存入通道的。
许多模式可看成Producer-Consumer模式的一个实例。
该模式也被称为Concurrent Object模式。
客户端代码需要访问独立的线程控制( Thread of Control)对象。
客户端代码需要使用某个类提供的服务,但是不希望等待相应的服务调用完成后才能继续其他处理,以避免响应性和吞吐率受此服务调用的影响。
将服务方法的调用 (Invocation)和执行(Execution)进行解耦(Decoupling)。客户端代码调用某个服务方法时,该方法并不立即执行相应的服务操作,而是生成表示相应服务操作的对象(任务)并将其存入缓存区,由专门的工作者线程取缓冲区中的任务进行执行。
有利于提高并发性,从而提高系统的吞吐率。使调试变得复杂。
Active Object模式可看成Producer-Consumer模式的一个实例。
Active Object模式使用了Promise模式以实现客户端代码获取异步任务的处理结果。
无
多线程环境中,新的任务不断产生。
为每个新的任务都创建一个线程去负责处理过程会导致线程不断地被创建和销毁,这会增加系统的资源消耗和上下文切换。
将待处理的任务存入缓冲区,并创建一定数量的工作者线程,复用这些工作者线程使其从缓冲区中取出任务执行。
抵消线程创建的开销,提高系统的响应性。封
装了工作者线程生命周期管理。
减少销毁线程的开销。
不恰当的使用可能导致死锁。
Thread Pool模式可看成Producer-Consumer模式的一个实例。
Thread Pool模式可以使用Two-phase Termination模式来实现其工作者线程的停止。
该模式也被称为Thread Local Storage模式。
多个线程需要访问同一个非线程安全对象。或者,使用线程安全的对象,但希望能够避免其使用的锁的开销。
多个线程访问同一个非线程安全对象(TSObject)可能产生线程安全问题,而我们又不希望因此而引入锁,以便能够避免锁的开销和相关问题。
使每个线程获得一个(且仅一个)该线程所特有的 TSObject 实例,各个线程仅访问各自的TsObject实例,一个TSObject实例不会被多个线程共享。
lmmutable Object模式和Serial Thread Confinement模式也能够在不引入锁的情况下确保线程安全。
别名
无
异步编程中,工作者线程需要访问非线程安全对象,而我们又不希望因此而引人锁。
系统对某种并发任务的处理涉及非线程安全对象的访问,而我们又不希望因此而引入锁,以便能够避免锁的开销和相关问题。
将并发任务通过队列串行化,再创建唯一的一个工作者线程对队列中的任务进行执行。
在不引入锁的情况下实现了对非线程安全对象访问的线程安全。
如果客户端代码关心任务的处理结果,那么可能导致多个客户端线程等待同一个工作者线程的处理结果。
Serial Thread Confinement模式可着成Producer-Consumer模式的一个实例。Immutable Object模式和Thread Specific Storage模式也能够在不引入锁的情况下确保线程安全。
如果客户端代码关心任务的处理结果,那么我们可以借用Promise模式来实现这点。
该模式也被称为Boss-Worker(老板-伙计)模式。
一个任务被分解为等同语义(Semantically-identical)的若干个子任务。
分而治之(Divide and Conquer)是解决许多问题的一个通用原则。将一个任务(原始任务)分解为若干个子任务,再让这些子任务独立执行。然后将各个子任务的处理结果组合成原始任务的处理结果。这个过程需要处理好以下几个方面。
客户端代码不应该知道其调用的服务是基于分而治之的计算。
无论是客户端代码还是子任务,它们都应该不依赖于任务分解和子任务处理结果合并的算法。
在服务的客户端代码和子任务的处理之间引入一个协调性的对象(即 Master)。有关分而治之的相关细节被封装在Master里面。各个子任务由专门的工作者线程负责处理。
Master-Slave模式可看成Producer-Consumer模式的一个实例。
Master-Slave模式中的Master参与者可能会使用Promise模式以获取子任务的处理结果。
无
多线程编程中,规模较大的任务的处理可以分解为若干个存在依赖关系的子任务。
规模较大的任务(原始任务)的处理可能比较耗时。如果对原始任务进行纵向分解,即分解得来的子任务中的每个任务的处理又包括若干个步骤,那么即使我们采用若干个工作者线程去负责执行子任务的执行也仍然避免不了一个子任务的处理中所出现的等待(一个处理步骤的开始要等待前一个处理步骤的完成)。
对原始任务进行横向分解,即将一个任务的处理分解为若干个处理阶段(Stage),其中每个处理阶段的输出作为下一个处理阶段的输入,并且各个处理阶段都有相应的工作者线程去执行相应计算。
Pipeline模式中的处理阶段可能会使用Serial Thread Confinement模式,以实现任务处理的线程安全。
Pipeline模式可以借助Master-Slave模式实现某个处理阶段的并行处理。
无
某计算同时涉及低级(或耗时较短)任务和高级(或耗时较长)任务。
低级(或耗时较短)的任务可以直接在客户端线程中执行,但是高级(或耗时较长)任务在客户端线程中执行则会增加客户端线程的等待从而减少吞吐率并降低响应性。
采用分层架构。将低级(或耗时较短)任务放在异步层由客户端线程执行,高级(或耗时较长)任务放在同步层由专门的后台工作者线程执行。异步层和同步层不直接通信,而是通过队列层进行通信。
Half-sync/Half-async模式可看成Producer-Consumer模式的一个实例。
Half-sync/Half-async模式可能会使用Two-phase 'Termination模式来停止后台工作者线程。
Half-sync/Half-async模式的队列层和同步层合起来可以使用Active Object模式来实现。
Thread Pool模式可以用来实现同步层任务的执行。
设计模式并不是孤立的,一个设计模式往往和其他设计模式存在某些关联,如图所示。
设计模式之间的关系可以概括为:支持、变体、组合和备选这4种关系。