读书笔记《spring-5-design-patterns》第 12 章实现并发模式
第 12 章实现并发模式
在第11章中,实现响应式设计模式,我们讨论了响应式设计模式以及它如何满足当今应用程序的要求。 Spring 5 Framework 为 Web 应用程序引入了响应式 Web 应用程序模块。在本章中,我们将探讨一些并发设计模式以及这些模式如何解决多线程应用程序的常见问题。 Spring 5 Framework 的响应式模块也为多线程应用程序提供了解决方案。
如果您是一名软件工程师或正在成为一名软件工程师,那么您必须了解术语并发。在几何属性中,并发的圆或形状是具有共同中心点的那些形状。这些形状的尺寸可能不同,但有一个共同的中心或中点。
这个概念在软件编程方面也是类似的。术语并发编程在技术或编程中是指程序并行执行多个计算的能力以及程序处理的能力在单个时间间隔内发生的多个外部活动。
正如我们在软件工程和编程方面谈论的那样,并发模式是那些有助于处理多线程编程模型的设计模式。一些并发模式如下:
- Handling concurrency with concurrency patterns
- Active object pattern
- Monitor object pattern
- Half-Sync/Half-Async patterns
- Leader/followers pattern
- Thread-specific storage
- Reactor pattern
- Best practices for concurrency module
现在让我们深入探讨这五种并发设计模式。
活动对象模式
concurrency 设计模式的活动对象类型将方法执行与方法调用区分开来。这种模式的作用是增强并发性,同时简化对驻留在独立且可区分的控制线程中的对象的同步访问。它用于处理同时到达的多个客户端请求,也用于提高服务质量。让我们看下图,它说明了并发和基于多线程的应用程序中的活动对象设计模式:
正如您在上图中所见,此 concurrency 设计模式的以下组件:
- Proxy: This is the active object that is visible to the client. The proxy advertises its interface.
- Servant: There is a method that is defined in the interface of the proxy. The servant is the provider of its implementation.
- Activation list: This is a serialized list that contains method request objects that the proxy inserts. This list allows the servant to run concurrently.
那么,这种设计模式是如何工作的呢?嗯,这个问题的答案是每个并发对象都属于或驻留在一个单独的控制线程中。这也独立于客户端的控制线程。这会调用其中一种方法,这意味着方法执行和方法调用都发生在不同的控制线程中。但是,客户将此过程视为普通方法。为了让代理在运行时将客户端的请求传递给仆人,两者都必须在单独的线程中运行。
在这种设计模式中,代理收到请求后所做的就是建立一个方法请求对象并将其插入到激活列表中。该方法执行两项工作;保存方法请求对象并跟踪它可以执行的方法请求。请求参数和任何其他信息都包含在方法请求对象中,以便稍后执行所需的方法。作为回报,这个激活列表帮助代理和仆人同时运行。
让我们在接下来的部分中了解另一种并发设计模式,即监控对象模式。
监控对象模式
监视器对象模式是另一种并发设计模式 有助于执行多线程程序。它是一种设计模式,旨在确保在单个时间间隔内,只有一个方法在单个对象中运行,为此,它同步并发方法执行。
与活动对象设计模式不同,监控对象模式没有单独的控制线程。收到的每个请求都在客户端本身的控制线程中执行,直到方法返回时,访问被阻止。在单个时间间隔内,可以在一个监视器中执行单个同步方法。
监控对象模式提供了以下解决方案:
- The synchronization boundaries are defined by the interface of the object, and it also makes sure that a single method is active in a single object.
- It must be ensured that all the objects keep a check on every method that needs synchronization and serialize them transparently without letting the client know. Operations, on the other hand, are mutually exclusive, but they are invoked like ordinary method calls. Wait and signal primitives are used for the realization of condition synchronization.
- To prevent the deadlock and use the concurrency mechanisms available, other clients must be allowed to access the object when the method of the object blocks during execution.
- The invariants must always hold when the thread of control is interrupted voluntarily by the method.
让我们看下图,它更详细地说明了并发应用程序中的监控对象设计模式:
在上图中,客户端对象调用具有多个同步方法的监视器对象以及与监视器条件和监视器锁关联的监视器对象。让我们探索这个并发设计模式的每个组件,如下所示:
- Monitor object: This component exposes the methods that are synchronized to the clients
- Synchronized methods: The thread-safe functions that are exported by the interface of the object are implemented by these methods
- Monitor conditions: This component along with the monitor lock decides whether the synchronized method should resume its processing or suspend it
活动对象和监控对象模式是并发设计模式的分支。
现在,我们将讨论的另一种并发模式是并发架构模式的分支。
半同步/半异步模式
Half-Sync和Half-Async的作用是区分异步和同步两种处理,在不影响程序性能的情况下简化程序。
为异步和同步服务引入了相互通信的两层,目的是在两者之间使用排队层进行处理。
每个并发系统都包含异步和同步服务。为了使这些服务能够相互通信,Half-Sync/Half-Async 模式将系统中的服务分解为层。使用排队层,这两个服务相互传递消息以进行相互通信。
让我们看一下说明这些设计模式的下图:
如上图所示,共有三层——同步服务层、队列层和异步服务层。同步层包含与 队列层的队列同步工作的服务,并且此查询使用 异步服务层。这一层的这些异步服务正在使用基于事件的外部资源。
如上图所示,此处包含三层。让我们看看这些层:
- Synchronous Task Layer: The tasks in this layer are active objects. High-level input and output operations are carried by these tasks, which transfer the data synchronously towards the queuing layer.
- Queuing Layer: This layer provides the synchronization and buffering required between the synchronous and asynchronous task layers.
- Asynchronous Task Layer: The events from the external sources are handled by the tasks present in this layer. These tasks do not contain a separate thread of control.
我们已经讨论了并发模式的半同步和半异步设计模式。让我们转向另一种并发模式,即领导者/跟随者模式。
领导者/追随者模式
事件源中服务请求的检测、解复用、分派和处理在并发模型中以高效的方式进行,其中许多多个线程一个一个地处理以使用事件源上的集合.半同步/半异步的另一个替代品是领导者/跟随者模式。可以使用此模式代替 Half-Sync/Half-Async 和活动对象模式,以提高性能。使用它的条件是在处理多线程请求时不能有排序和同步约束:
此模式的重点工作是同时或同时处理多个事件。由于与并发相关的开销,可能无法将单独的线程与每个套接字句柄连接起来。这种设计的突出特点是,通过使用这种模式,解复用线程和事件源之间的关联成为可能。当事件到达事件源时,此模式会建立一个线程池。这样做是为了有效地共享一组事件源。这些事件源依次解复用到达的事件。此外,事件被同步分派到应用程序服务进行处理。在leader/follower模式构造的线程池中,只有一个线程等待事件的发生;其他线程排队等待。当线程检测到事件时,追随者被提升为领导者。然后它处理线程并将事件分派给应用程序处理程序。
在这种模式中,处理线程可以并发运行,但只允许一个线程等待即将到来的新事件。
让我们在接下来的部分中了解另一种基于并发的设计模式。
反应堆模式
reactor 模式用于处理服务处理程序从单个或多个输入源同时接收的服务请求。接收到的服务请求然后由服务处理器解复用并分派给相关的请求处理器。所有反应器系统通常都存在于单线程中,但据说它们也存在于多线程环境中。
使用这种模式的主要好处是应用程序组件可以分为多个部分,例如模块化或可重用。此外,这允许简单的粗粒度并发,而不会给系统带来额外的多线程复杂性。
让我们看一下关于反应器设计模式的下图:
正如您在上图中所见,调度程序使用解复用器通知处理程序,处理程序执行要通过 I/O 事件完成的实际工作。反应器通过调度适当的处理程序来响应 I/O 事件。处理程序执行非阻塞操作。上图具有此设计模式的以下组件:
- Resources: These are the resources through which input is provided or output is consumed.
- Synchronous event demultiplexer: This blocks all resources via an event loop. When there is a possibility that a synchronous operation will start, the resource is sent to the dispatcher through the demultiplexer without blocking.
- Dispatcher: The registering or unregistering of request handler is handled by this component. Resources are dispatched to the respective request handler through the dispatcher.
- Request Handler: This handles the request dispatched by the dispatcher.
现在,我们将进入下一个也是最后一个并发模式,即线程特定的存储模式。
线程特定的存储模式
可以使用单个逻辑全局访问点来检索线程本地的对象。这种并发设计模式允许多个线程执行此功能。这样做不会在每次访问对象时产生锁定开销。有时,这种特定模式可以被视为所有并发设计模式的对立面。这是由于线程特定存储通过防止线程之间共享可用资源来解决几个复杂性的事实。
该方法似乎是由应用程序线程在普通对象上调用的。实际上,它是在特定于线程的对象上调用的。多个应用程序线程可以使用单个线程特定对象代理来访问与它们中的每一个相关联的唯一线程特定对象。用于区分它封装的线程特定对象的代理使用应用程序线程标识符。
并发模块的最佳实践
以下是程序员在执行并发时必须考虑的一系列注意事项。当您有机会使用并发应用程序模块时,让我们看看以下最佳实践。
- Obtaining an executor: The Executor Framework for obtaining an executor supplies the executors utility class. Various types of executors offer specific thread executions policies. Here are three examples:
- ExecutorService newCachedThreadPool(): This creates a thread pool using the previously constructed threads if available. The performance of the programs that make use of the short-lived asynchronous tasks is enhanced using this type of thread pool.
- ExecutorService newSingleThreadExecutor(): A worker thread that is operating in an unbounded queue is used here to create an executor. In this type, the tasks are added to the queue that is then executed one by one. In case, this thread fails during the execution, a new thread will be created and replace the failed thread so that the tasks can be executed without interruption.
- ExecutorService newFixedThreadPool(int nThreads): A fixed number of threads that are operating in a shared unbounded queue are reused in this case for the creation of a thread pool. At threads, the tasks are being actively processed. While all the threads in the pool are active and new tasks are submitted, the tasks will be added in the queue until a thread becomes available for the processing of the new task. If before the shutdown of the executor, the thread fails, a new thread will be created for carrying out the execution of the task. Note that these thread pools exist only when the executor is active or on.
- Use of cooperative synchronized constructs: It is recommended to use cooperative synchronized constructs when possible.
- No unnecessary lengthy tasks and oversubscription: Lengthy tasks are known to cause deadlock, starvation, and even prevent other tasks from functioning properly. Larger tasks can be broken down into smaller tasks for proper performance. Oversubscription is also a way to avoid the deadlock, starvation, and so on. Using this, more threads than the available number of threads can be created. This is highly efficient when a lengthy task contains a lot of latency.
- Use of concurrent memory-management functions: If in a situation, ensuing concurrent memory management functions can be used, it is highly recommended to use it. These can be used when objects with a short lifetime are used. The functions such as
Allot
andFree
are used to free memory and allocate, without memory barriers or using locks. - Use of RAII to manage the lifetime of concurrency objects: RAII is the abbreviation for Resource Acquisition Is Initialization. This is an efficient way to manage the lifetime of a concurrency object.
这都是关于并发的,它是可用于处理和实现并发的设计模式。这些是并发程序最常见的五种设计模式。此外,还讨论了执行并发模块的一些最佳实践。希望这是一篇内容丰富的文章,并帮助您了解并发模式的工作原理!
概括
在本章中,您学习了几种并发设计模式,并看到了这些模式的用例。在本书中,我只介绍了并发设计模式的基础知识。我们包括了活动对象、监控对象、半同步/半异步、领导者/追随者、线程特定的存储和反应器模式。这些都是应用程序多线程环境中并发设计模式的一部分。我们还讨论了在应用程序中使用并发设计模式的一些最佳实践考虑。