当前位置: 首页 > news >正文

Java多线程设计模式之保护性暂挂模式

模式简介

多线程编程中,为了提高并发性,往往将一个任务分解为不同的部分。将其交由不同的线程来执行。这些线程间相互协作时,仍然可能会出现一个线程等待另一个线程完成一定的操作,其自身才能继续运行的情形。

保护性暂挂模式(Guarded Suspension)可以帮助我们解决上述的等待问题。该模式的核心思想是如果某个线程执行特定的操作前需要满足一定的条件,则在该条件未满足时将该线程暂停运行(即暂挂线程,使其处于waiting状态,直到该条件满足时才继续运行)。wait/notify可以用来实现保护性暂挂模式,但是,该模式还要解决wait/notify所解决的问题之外的问题。

模式架构

保护性暂挂模式的 核心是一个受保护的方法,该方法执行其所要真正执行的操作时需要满足特定的条件(Predicate),当条件不满足时,执行受保护方法的线程会被挂起进入等待状态,直到该条件满足时线程才会继续运行。 其类图如下所示:

在这里插入图片描述

  1. GuardedObject:包含受保护方法的对象,其主要方法及职责如下:

    • guardedMethod:受保护方法
    • stateChanged:改变GuardedObject实例状态的方法,该方法负责在保护条件成立时唤醒受保护方法的执行线程。
  2. GuardedAction:抽象了目标动作(受保护方法所要执行的操作),关联了目标动作所需的保护条件。其主要方法及职责如下

    • call:用户表示目标动作的方法
  3. ConcreteGuardedAction:应用程序所实现的具体目标动作极其关联的保护条件。

  4. Predicate:抽象了保护条件

    • evaluate:用于表示保护条件的方法
  5. ConcretePredicate:应用程序所实现的具体保护条件。

  6. Blocker:负责对执行guardedMethod的线程进行挂起和唤醒,并执行ConcreteGuardedAction所实现的目标操作,其方法及职责如下:

    • callWithGuard :负责执行目标操作和暂挂当前线程。
    • signalAfter:负责执行其参数指定的动作和唤醒由该方法所属Blocker实例所暂挂的线程中的一个线程。
    • signal:负责唤醒由该方法所述Blocker实例所暂挂的线程中一个线程。
    • broadcastAfter:负责执行其参数指定的动作和唤醒由该方法所属Blocker实例所暂挂的所有线程。
    • broadcast:负责唤醒由该方法所属Blocker实例暂挂的所有线程。
  7. ConditionVarBlocker:基于Java条件变量(java.util.concurrent.locks.Condition)实现的Blocker。

    其时序图如下
    在这里插入图片描述

    1. 客户端代码调用受保护方法guardedMethod。
    1. guardedMethod方法创建guardedAction实例
    1. guardedMethod 方法以guardedAction为参数调用Blocker实例的callWithGuard方法。
  • 4-5. callWithGuard方法调用guardedAction的getGuard方法获取保护条件Predicate。
  • 6-8. 循环判断保护条件是否成立,若保护条件成立,则循环退出。否则,循环将当前线程暂挂使其处于等待状态。当其他线程唤醒被暂挂的线程后,该循环仍然继续检测保护条件,并重复上述逻辑。
  • 9-10.callWithGuard方法调用guardedAction的call方法来执行目标动作,并记录call方法的返回值。
    1. callWithGuard将返回值返回给调用方。
    1. guardedMethod方法返回。

代码示例:

  1. GuardedObject中的guardedMethod方法。
    // 1. sendAlarm是一个受保护方法public void sendAlarm(final AlarmInfo alarm) throws Exception {// 2. 创建guardedAction实例GuardedAction<Void> guardedAction =new GuardedAction<Void>(agentConnected) {public Void call() throws Exception {doSendAlarm(alarm);return null;}};//  3. 调用Blocker实例的callWithGuard方法。blocker.callWithGuard(guardedAction);}
  1. blocker实例中的callWithGuard方法
public <V> V callWithGuard(GuardedAction<V> guardedAction) throws Exception {lock.lockInterruptibly();V result;try {//  4-5. callWithGuard方法调用guardedAction的getGuard方法获取保护条件Predicate。final Predicate guard = guardedAction.guard;while (!guard.evaluate()) {//6-8:循环判断保护条件是否成立Debug.info("waiting...");condition.await();}// 9-10 callWithGuard方法调用guardedAction的call方法来执行目标动作,并记录call方法的返回值。result = guardedAction.call();return result;} finally {lock.unlock();}}

受保护方法的执行线程被暂挂后,当保护条件成立时,其他线程需要唤醒该线程.其序列图如下

在这里插入图片描述

    1. 客户端代码调用stateChanged方法改变GuardedObject实例状态,这些状态包含了保护条件所关心的状态。
    1. stateChanged方法创建java.util.concurrent.Callable实例stateOperation,stateOperation封装了改变GuardedObject实例状态所需的操作。
    1. stateChanged方法以stateOperation为参数,调用Blocker实例的signalAfter方法。
  • 4-5 signalAfter方法调用stateOperation对象的call方法以改变GuardedObject实例的状态,并记录其返回值shouldSignalBlocker
  • 6-7. signalAfter方法在shouldSignalBlocker值为true时,调用java.util.concurrent.locks.Condition实例的signal方法唤醒被该Condition实例所暂挂的线程中的一个线程。
    1. signalAfter方法返回,此时,执行受保护方法的线程可能已经被唤醒(取决于shouldSignalBlocker值是否为true)。

代码示例:

    protected void onConnected() {try {//2-3.创建stateOperation,调用Blocker实例的signalAfter方法blocker.signalAfter(new Callable<Boolean>() {@Overridepublic Boolean call() {connectedToServer = true;Debug.info("connected to server");return Boolean.TRUE;}});} catch (Exception e) {e.printStackTrace();}}public void signalAfter(Callable<Boolean> stateOperation) throws Exception {lock.lockInterruptibly();try {//4-5 signalAfter方法调用stateOperation对象的call方法以改变GuardedObject实例的状态if (stateOperation.call()) {//调用java.util.concurrent.locks.Condition实例的signal方法唤醒被该Condition实例所暂挂的线程中的一个线程。condition.signal();}} finally {lock.unlock();}}

模式的评价与实现考量

关注点分离,保护性暂挂模式中的各个参与者各自仅关注本模式所要解决的问题中的一个方面,各个参与者的职责是高度内聚的。这使得保护性暂挂模式便于理解和应用,应用开发人员只需要根据应用的需要实现GuardedObject、ConcretePredicate、和ConcreteGuardedAction这几个必须由应用实现的参与者即可,而其他参与者的实现都是可复用的。

可能增加JVM垃圾回收的负担,为了使GuardedAction实例的call方法能够访问保护方法guardedMethod参数,我们需要利用闭包。因此,GuardedAction实例可能是在保护方法中创建的,这意味着,每次保护方法被调用的时候都会有个新的GuardedAction实例被创建。而这会增加JVM内存池的占用,从而增加垃圾回收的负担。

可能增加上下文切换,这点与保护性暂挂模式本身无关。只不过,不管如何实现该模式,只要这里面涉及线程的暂挂和唤醒就会引起上下文切换。如果频繁出现保护方法被调用时保护条件不成立,那么保护方法的执行线程就会频繁的被暂挂和唤醒,从而导致频繁的上下文切换。

相关文章:

  • 关于Threejs的使用二
  • 东芝-Soft Limit 报警及其解决办法
  • 代码随想录算法训练营第29天(贪心)|455.分发饼干、376. 摆动序列、53. 最大子序和
  • C语言 图的基础知识
  • HTTP/2 协议学习
  • VMware ESXi 8.0U2c macOS Unlocker OEM BIOS ConnectX-3 网卡定制版 (集成驱动版)
  • 流程图工具评测:十大热门软件对比
  • Spring Boot 中如何解决跨域问题、Spring Cloud 5大组件、微服务的优缺点是什么?
  • 模拟原神圣遗物系统-小森设计项目,设计圣遗物(生之花,死之羽,时之沙,空之杯,理之冠)抽象类
  • 【软件测试】软件测试入门
  • AOSP开发环境搭建
  • 志愿服务管理系统的设计
  • 【全网最全最详细】RabbitMQ面试题
  • 【Go】用 DBeaver、db browser 和 SqlCipher 读取 SqlCipher 数据库
  • Java基础16(集合框架 List ArrayList容器类 ArrayList底层源码解析及扩容机制)
  • hexo+github搭建个人博客
  • JavaScript-如何实现克隆(clone)函数
  • Create React App 使用
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • HTTP中的ETag在移动客户端的应用
  • js作用域和this的理解
  • Just for fun——迅速写完快速排序
  • 记录:CentOS7.2配置LNMP环境记录
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 那些被忽略的 JavaScript 数组方法细节
  • 我看到的前端
  • 学习JavaScript数据结构与算法 — 树
  • 找一份好的前端工作,起点很重要
  • postgresql行列转换函数
  • ​1:1公有云能力整体输出,腾讯云“七剑”下云端
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • #07【面试问题整理】嵌入式软件工程师
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • (day6) 319. 灯泡开关
  • (二十九)STL map容器(映射)与STL pair容器(值对)
  • (附源码)spring boot儿童教育管理系统 毕业设计 281442
  • (一)、python程序--模拟电脑鼠走迷宫
  • (转载)深入super,看Python如何解决钻石继承难题
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .net core docker部署教程和细节问题
  • .NET Core 项目指定SDK版本
  • .NET WPF 抖动动画
  • .net 调用php,php 调用.net com组件 --
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • [ 手记 ] 关于tomcat开机启动设置问题
  • [AI Google] Ask Photos: 使用Gemini搜索照片的新方法
  • [android] 手机卫士黑名单功能(ListView优化)
  • [AX]AX2012 R2 出差申请和支出报告
  • [BFS广搜]迷阵
  • [c++] 什么是平凡类型,标准布局类型,POD类型,聚合体
  • [C++基础]-初识模板
  • [CodeForces-759D]Bacterial Melee
  • [CSS]中子元素在父元素中居中
  • [daily][archlinux][game] 几个linux下还不错的游戏
  • [DAX] MAX函数 | MAXX函数