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>() {
                @Override
                public 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内存池的占用,从而增加垃圾回收的负担。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/727836.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NodeJs 连接本地 mySql 数据库获取数据

写在前面 今天把 nodejs 连接本地数据库的坑简单的踩一下&#xff0c;为后续写接口做个铺垫 安装 mySql &#xff08;mac举例子&#xff09; 安装地址 安装完成大概这个样子&#xff0c;起动起来就行 安装本地数据库连接工具&#xff08;navicat举例子&#xff09; 安装地…

文件防篡改监控工具 - WGCLOUD全面介绍

WGCLOUD是一款优秀的运维监控软件&#xff0c;免费、轻量、高效&#xff0c;部署容易&#xff0c;上手简单&#xff0c;对新手非常友好 WGCLOUD部署完成后&#xff0c;点击菜单【文件防篡改】&#xff0c;可以看到如下页面 我们点击【添加】按钮&#xff0c;输入监控文件的信息…

赛力斯:“新王”能做多久

最近&#xff0c;电车圈又有大事了。 造车新势力们迎来“新王”——赛力斯。 最近&#xff0c;赛力斯市值突破1500亿&#xff0c;反超理想&#xff0c; 成为新势力市值一哥。 今年第一季度&#xff0c;赛力斯新能源汽车销量达94825辆&#xff0c;同比增长高达374.77%&#xf…

想要高效回复客户消息?来看看这个款微信神器

不管是销售还是客服来说&#xff0c;能及时回复客户的反馈和问题&#xff0c;是确保顾客满意度的关键因素。 今天&#xff0c;就给大家分享一个职场必备神器——个微管理系统&#xff0c;帮助大家提高回复效率&#xff01; 首先&#xff0c;你可以在系统上设置自动通过好友后自…

聊聊其他之ShowDoc安装部署

聊聊其他之ShowDoc安装部署 Docker离线安装部署 由于很多公司服务器处于内网环境&#xff0c;跟外网阻断&#xff0c;所以需要通过离线的方式进行Docker镜像安装。 Linux环境准备 第一步&#xff1a;检查防火墙&#xff0c;是否关闭。 查看防火墙状态&#xff1a; [rootlo…

java后端方法地址组成解析

本篇文章旨在记录后端方法被调用时&#xff0c;是如何组成的&#xff0c;以及组成的部分。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、后端方法地址是什么&#xff1f; 示例&#xff1a;http://127.0.0.1:8080/user/info 如果携带了路径参数…

主存储器的基本组成+容量扩展+与CPU的连接

1.基本组成 1.主存储器的基本组成和读写操作 主存储器被称为主存/内存。是计算机中存储程序的重要部件 主存储器内部包含了存储体、各种逻辑部件以及控制电路等。 主存是通过寻址的方式对存储体内的存储单元进行读写操作的。 主存首先要从MAR获取地址&#xff0c;之后译码器…

Java23种设计模式(五)

1、MVC 模式 MVC 模式代表 Model-View-Controller&#xff08;模型-视图-控制器&#xff09; 模式。这种模式用于应用程序的分层开发。 Model&#xff08;模型&#xff09; - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑&#xff0c;在数据变化时更新控制器。…

惠海H4120 降压IC 40V 36V 30V降压5V3A 9V3A 12V3A 动态响应优异,低纹波

H4120是一款功能优良的异步降压型DC-DC转换器。它的主要特性和优势如下&#xff1a; 产品特性&#xff1a; 内置40V耐压MOS&#xff1a;内置的高耐压MOS使得H4120能够处理更多种的输入电压范围&#xff0c;增强了其适用性和可靠性。 宽输入范围&#xff1a;输入电压可在5V至…

ubuntu18.04 安装HBA

HBA是一个激光点云层级式的全局优化的程序&#xff0c;他的论文题目是&#xff1a;HBA: A Globally Consistent and Efficient Large-Scale LiDAR Mapping Module&#xff0c;对应的github地址是&#xff1a;HKU-Mars-Lab GitHub 学习本博客&#xff0c;可以学到gtsam安装&am…

android串口助手apk下载 源码 演示 支持android 4-14及以上

android串口助手apk下载 1、自动获取串口列表 2、打开串口就开始接收 3、收发 字符或16进制 4、默认发送at\r\n 5、android串口助手apk 支持android 4-14 &#xff08;Google seral port 太老&#xff09; 源码找我 需要 用adb root 再setenforce 0进入SELinux 模式 才有权限…

QT day03

思维导图 QT设计 升级优化自己应用程序的登录界面。 要求&#xff1a; 1. qss实现 2. 需要有图层的叠加 &#xff08;QFrame&#xff09; 3. 设置纯净窗口后&#xff0c;有关闭等窗口功能。 4. 如果账号密码正确&#xff0c;则实现登录界面关闭&#xff0c;另一个应用界面…

vuejs3+elementPlus后台管理系统,左侧菜单栏制作,跳转、默认激活菜单

默认激活菜单,效果&#xff1a; 默认激活菜单&#xff0c;效果1&#xff1a; 默认激活菜单&#xff0c;效果2&#xff1a; 跳转链接效果&#xff1a; 制作&#xff1a; <script setup> import {useUserStore} from "/stores/userStore.js"; import {ref} fr…

密码学及其应用——GMP库在密码学中的应用

GMP&#xff08;GNU Multiple Precision arithmetic library&#xff0c;GNU多精度算术库&#xff09;是一个针对大整数运算的库。这个库提供了许多针对多种多精度类型的计算函数&#xff1a; - 大整数&#xff1a;Z - 大有理数&#xff1a;Q - 大浮点数&#xff1a;R 1. 密码学…

找不到com.fasterxml.jackson.core.exc.StreamWriteException的类文件

1. 前言: 使用springboot搭建的项目, 需要使用 jackson 更改json文件的内容; maven管理jar包, 导入jar包版本信息如下: <!-- 读写json文件所需依赖 --> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databin…

陈晓婚前婚后大变样

陈晓婚前婚后大变样&#xff1f;陈妍希揭秘甜蜜与现实的碰撞在娱乐圈的星光璀璨中&#xff0c;有一对夫妻总是津津乐道&#xff0c;那就是陈晓和陈妍希。他们的爱情故事&#xff0c;从荧幕到现实&#xff0c;一直备受关注。然而&#xff0c;近日陈妍希在节目中透露&#xff0c;…

yolov8训练文件夹文件目录介绍及讲解

背景说明 凡是使用过yolov8算法的朋友都知道&#xff0c;在使用yolov8算法训练模型完成后&#xff0c;会在代码目录下默认生成一个runs文件夹&#xff0c;该文件夹通常用来保存模型的训练任务以及相关的模型信息。 如果我们按照任务分类进行点击进入&#xff0c;会发现…

实现一个简易动态线程池

项目完整代码&#xff1a;https://github.com/YYYUUU42/Yu-dynamic-thread-pool 如果该项目对你有帮助&#xff0c;可以在 github 上点个 ⭐ 喔 &#x1f970;&#x1f970; 1. 线程池概念 2. ThreadPoolExecutor 介绍 2.1. ThreadPoolExecutor是如何运行&#xff0c;如何同时…

【第22章】Vue实战篇之文章分类

文章目录 前言一、文章分类列表查询1. 界面2. 脚本3. 展示 二、文章分类添加1. 界面2. 接口脚本3. 点击事件 三、文章分类编辑1. 界面2. 接口脚本3. 点击事件 四、文章分类删除1. 界面2. 接口脚本3. 点击事件 总结 前言 这里来学习文章分类相关界面和接口的调用(增删改查)。 一…

新版二开微信发卡小程序源码卡密系统/支持流量主

新版二开微信发卡小程序源码卡密系统支持流量主。裂变扩展多种领取模式二次开发的发卡小程序源码&#xff0c;其后台采用PHP编写&#xff0c;支持用户通过付费购卡或者观看视频广告领取卡密。 该小程序还支持流量主&#xff0c;因为功能需要&#xff0c;就进行了二开&#xff…