Java|Java 多线程设计模式概述

【Java|Java 多线程设计模式概述】设计模式(Design Pattern):是软件设计中给定背景(context)下普遍存在的问题一般性可复用的解决方案。
优点:
(1)不使用锁的情况下保证线程安全:immutable object(不可变对象)模式,thread sprcific storage(线程特有存储)模式,serial thread confinement(串行线程封闭)模式。
(2)优雅的停止线程:two——phase termination(两阶段终止)模式。
(3)线程写作:Guarded Suspension(保护性暂挂)模式、Producer-Consumer(生产者消费者)模式。
(4)提高并发行(Concurrency)、减少等待:Promise(承诺)模式、Active Object(主动对象)模式、Pipeline(流水线模式)
(5)提高响应性(Responsiveness):Master-Slave(主仆)模式、Half-sync/Half-async(半同步/半异步)模式。
(6)减少资源消耗:Thread Pool(线程池)模式、Serial Thread Confinement(串行线程封闭)模式。
模式简介说明:
Immutable Object(不可变对象)模式
别名:该模式也被称为Immutable(不可变)模式
背景:多个线程共享一个对象的实例。
问题:当被共享的对象相应的现实世界实体的状态变更时,系统对此要有所反映。但是通过直接更改该被共享的对象的状态来反映这个通常会导致所的引入以保证线程安全。
解决方案:将相应现实世界实体建模为状态不可变的对象。当相应显示世界实体的状态变更时,系统通过创建新的对象实例来反应这种状态变更,而不是更改对象本身的状态。
结果:
多个线程可以在不使用所的状况下,以线程安全的方法去访问共享对象。
可能导致频繁的创建对象
相关模式:Thread Specific Storage模式、Serial Thread Confinement模式
Guarded Suspension(保护性暂挂)模式
别名:该模式也被称为Guarded Wait(受保护等待)模式
背景:一个方法与执行的操作(目标动作)所需的前提条件可能暂时无法满足而稍后可能得以满足。
问题:多线程环境中,某个对象的方法被调用时,该方法与执行的操作所需状态暂时没有得到满足,而稍后可能得以满足。因此,此时如果该方法返回或者抛出异常,则会迫使客户端代码对其不期望的结果进行处理。
解决方案:多线程环境中,当某个对象的方法(受保护方法)执行其欲执行的操作所需的状态(保护条件)违背满足时,将当前线程暂挂知道其他线程改变了该对象的状态使得保护条件得以满足时,被暂挂的线程得以唤醒。
结果:
使应用程序避免了样板式代码。
实现了关注点分离
可能增加了JVM垃圾回收的负担
可能增加了上下文切换
相关模式:Promise模式、Producer-Consumer模式实现过程中可能需要使用Guarded Suspension模式。
Two-phase Termination(两阶段终止)模式
别名:无
背景:系统需要防止用户线程(User Thread)阻止Jvm正常关闭
问题:守护线程(Daemon Thread)不会阻止JVM正常关闭,而用户线程会阻止JVM正常关闭。因此,正常关闭JVM时需要先将用户线程停止。但是,停止一个用户线程时,我们希望该线程能够在其处理完待处理的任务后再停止。
解决方案:将线程的停止分为两个阶段:准备阶段和执行阶段。准备阶段主要实现线程停止的标志的设置,执行阶段主要实现线程停止标志的检测并在线程处理完待处理的任务后停止。
结果:
实现了线程的优雅停止:线程可以在其处理完待处理的任务后再行停止,而非粗暴的停止。
可能延迟线程的停止:待停止的线程可能是在其处理完待处理的任务后再停止的,而这可能需要一定的等待时间。
相关模式:Producer——Consumer模式、Master——Slave模式可能需要使用Two——phase Termination模式以实现其工作者线程停止。
Promise(承诺)模式
别名:该模式也被称为Futu(期货)模式
背景:一个对象需要使用另外一个对象的某个方法(一下称为目标方法)的返回值。
问题:目标方法需要消耗较长的处理时间才能返回表示处理结果的值。在该方法返回之前,客户端代码会被阻塞而无法进行其他处理
解决方案:使用异步编程,将目标方法的返回值改为一个凭据对象,而不是表示目标方法真正处理结果的对象(结果对象)。客户端代码通过调用凭据对象的某个方法来获取目标方法的结果对象。再次基础上、采用专门的工作者线程或者线程池去执行目标方法所进行的计算。
结果:
既发挥了异步编程的优势——增加系统的并发性,减少不必要的等待,又保持了同步编程的简单性——客户端代码的编写方式与同步编程无本质差别。
相关模式:目标方法可以堪称Factory Method 模式中的工厂方法
凭据对象用于获取结果对象的方法可能需要等待目标方法对应的计算完成才能发挥,着可以使用Guarded Suspension 模式来实现。
Active Object 模式可以看成是包含了Promise模式的复合模式,其异步方法的返回值是一个凭据对象。
Producer——Consumer(生产者/消费者)模式
别名:无
背景:数据(任务)的提供方的处理能力与相应的使用方的处理能力不均衡,或者我们不希望二者的处理能力相互影响。
问题:数据(任务)的提供方和使用方运行同一个线程中会导致一方的处理能力的大小对另外一方产生影响,即造成等待
解决方案:在数据(任务)的提供方和使用方之间引入一个作为缓冲区的通道,从而使数据(任务)的提供方和使用方可以运行在各自的线程之中。
结果:
数据(任务)的提供方和使用方的处理能力相对来说互不影响
关注点分离(Separation of Concern):数据(任务)的提供方只需要将数据存入通道即可,他无需关心谁对数据进行处理;而数据的使用方只需要把数据从通道取出即可,而无须关心数据是谁存进通道的。
相关模式:许多模式可以看成是Producer——Consumer模式的一个实例。
Active Object(主动对象)模式
别名:该模式也被称为Concurrent Object 模式。
背景:客户端代码需要访问独立的线程控制(Thread of Control)对象。
问题:客户端代码需要使用某个类提供的服务,但是不希望等待相应的服务调用完成后才能继续其他处理,以避免响应性和吞吐率受此服务调用的影响。
解决方案:将服务方法的调用(Invocation)和执行(Execution)进行解耦(Decoupling)。客户端代码调用某个服务方法时,该方法并不立即执行相应的服务操作,而是生成表示相应服务操作的对象(任务)并将其存入缓冲区,由专门的工作者线程取缓冲区中的任务进行执行。
结果:
有利于提高并发性,从而提高系统的吞吐率。
是调试变得复杂。
相关模式:Active Object 模式可看成Producer——Consumer模式的一个实例
Active Object 模式使用了Promise模式以实现客户端代码获取异步任务的处理结果。
Thread Pool(线程池)模式
别名:无
背景:多线程环境中,新的任务不断产生
问题:为每个新的任务都创建一个线程去负责处理过程会导致线程不断的被创建和销毁,这会增加系统的资源消耗喝上下文切换。
解决方案:将待处理的任务存入缓冲区,并创建一定数量的工作者线程,复用这些工作者线程使其从缓冲区中取出任务执行。
结果:
抵消线程创建的开销,提高系统的响应性。
封装了工作者线程生命周期管理。
减少销毁线程的开销。
不恰当的使用可能导致死锁。
相关模式:Thread Pool 模式可堪称Producer-Consumer模式的一个实例。
Thread Pool模式可以使用Two-phase Termination模式来实现其工作者线程的停止。
Thread Specific Storage(线程特有存储)模式
别名:该模式也被称为Thread Local Storage 模式
背景:多个线程需要访问同一个非线程安全对象。或者,使用线程安全的对象,但希望能够避免锁的开销。
问题:多个线程访问同一个非线程安全的对象可能产生线程安全问题、而我们又不希望因此而引入锁,以便能够避免锁的开销和相关问题。
解决方案:使每个线程获得一个(且仅一个)该线程锁特有的TSObject实例,各个线程仅访问各自的TSObject实例,一个TSObject实例不会被多个线程共享。
结果:
再不引入锁的情况下实现了对非线程安全对象访问的线程安全。
易于使用
隐藏了系统结构,可能使系统难于理解
鼓励了全局对象的使用。
不恰当的使用可能导致内存泄漏。
相关模式:Immutable Object模式和Serial Thread Confinement 模式也能够在不引入锁的情况下保证线程安全。
Serial Thread Confinement(串行线程封闭)模式
别名:无
背景:异步编程中工作者线程需要访问非线程安全对象,而我们又不希望一次引入锁。
问题:系统对某种并发任务的处理涉及非线程安全对象的访问,而我们又不希望一次而引入锁,一边能够避免锁的开销和相关问题。
解决方案:将并发任务通过队列串行化,再创建唯一的一个工作者线程对队列中的任务进行执行。
结果:
在不引入锁的情况下实现了对非线程安全对象访问的线程安全。
如果客户端代码关心任务的处理结果,那么可能导致多个客户端线程等待同一个工作者线程的处理结果。
相关模式:Serial Thread Confinement 模式可看成Producer—-Consumer模式的一个实例。
Immutable Object模式和Thread Specific Storage模式也能够在不引入锁的情况下保证线程安全。
如果客户端代码关心任务的处理结果,那么我们可以借用Promise模式来实现这点。
Master-Slave(主仆)模式
别名:该模式也被称为Boss-worker(老板-伙计)模式
背景:一个任务被分解为等同语义(semantically-identica)的若干个子任务
问题:分而治之(divide and conquer)是解决许多问题的一个通用原则。将一个任务(原始任务)分解为若干个子任务,再让这些子任务独立执行。然后将各个子任务的处理结果组合成原始任务的处理结果。这个过程需要处理好一下几个方面:
客户端代码不应该知道其调用的服务使基于分而治之的计算。
无论使客户端代码还是子任务,他们都应该不依赖于任务分解和子任务处理结果合并的算法。
解决方案:在服务的客户端代码和子任务的处理之间引入一个协调性的对象(即Master)。有关分而治之的相关细节被封装在Master里面。各个子任务由专门的工作者线程负责处理。
结果:
提升了计算性能:子任务可以并行执行。
可交换性(Exchangeability)和可扩展性(Extensibility):替换某个Slave实例、增加一个Slave实例对Master参与者产生的影响的影响很小。
相关模式:
Master-Slave模式可看成Producer-Consumer模式(第七章)的一个实例。
Master-Slave模式中的Master参与者可能会使用Promise模式(第6章)以获取子任务的处理结果。
Pipeline(流水线)模式
别名:无
背景:多线程编程中,规模较大的任务的处理可以分解为若干个存在以来关系的子任务。
问题:规模较大的任务(原始任务)的处理可能比较耗时。如果对原始任务进行纵向分解,即分解得来的子任务中的每个任务的处理又包括若干个步骤,那么即使我们采用若干个工作者线程去负责执行子任务的执行也任然避免不了下一个子任务的处理中所出现的等待(一个处理步骤的开始要等待前一个处理步骤的完成)。
解决方案:对原始任务进行横向分解,即将一个任务的处理分解为若干个处理阶段都有相应的工作者线程去执行相应计算。
结果:
可以对有依赖关系的任务实现并行处理。
为局部使用单线程模型编程提供便利。
具备任务处理逻辑安排的灵活性。
相关模式:Pipeline模式中的处理阶段可能会使用Serial Thread Confinement模式,以实现任务处理的线程安全。
Pipeline模式可以借助Master-Slave模式实现某个处理阶段的并行处理。
Half-sync/Half-async(半同步/半异步)模式
别名:无
背景:某计算同时涉及低级(或耗时较短)任务和高级(或耗时较长)任务
问题:低级的任务可以直接在客户端线程中执行,但是高级任务在客户端线程中执行会增加哦客户端线程的等待从而减少吞吐率并降低相应性。
解决方案:采用分层架构。将低级任务放在异步层由客户端线程执行,高级任务放在同步层由专门的后台工作者线程执行。异步层和同步层不直接通信,而是通过队列层进行通信。
结果:
即发挥了异步编程的优势——增加系统的并发行,减少不必要的等待,又保持了同步编程的简单性。
各层代码可以使用独立的并发访问控制策略。
相关模式:
Half-sync/Half-async模式可看成Producer-Consumer模式的一个实例。
Half-sync/Half-async模式可能会使用Two-Phase Termination模式来停止后台工作者线程。
Half-sync/Half-async模式的队列层和同步层合起来可以使用Active )Object模式来实现。
Thread Pool模式可以使用实现同步层任务的执行。

    推荐阅读