Java 多线程模式 —— Guarded Suspension 模式

2023-11-19

Part1Guarded Suspension 模式的介绍

我们只从字面上看,Guarded Suspension 是受保护暂停的意思。

1Guarded Suspension 模式

在实际的并发编程中,Guarded Suspension 模式适用于某个线程需要满足特定的条件(Predicate)才能执行某项任务(访问受保护对象)。条件未满足时,则挂起线程,让线程一直处于 WAITING 状态,直到条件满足后该线程才可以执行任务。有点类似于 Java 的 wait() 和 notify() 方法。

Guarded Suspension 模式通过让线程的等待,来保证受保护对象的安全。

5c3dc6a4f1ef12fb6082518d38f6648d.png
Guarded Suspension.png

Guarded Suspension 模式是等待唤醒机制的一种规范实现,又被称为 Guarded Wait 模式、Spin Lock 模式、多线程版本的 if。

2应用场景

Guarded Suspension 模式是多线程编程基础的设计模式,适用于很多应用场景,也可以和其他的多线程模式组合使用。

下面列举两个场景。

场景一

在工业自动化场景下,某个自动化流水线上使用工业相机通过图像算法识别上料台是否有物品,当算法识别到上料台有物品时,机械臂才会去抓取物品,否则机械臂一直处于等待状态。图像算法的调用和机械臂的控制,分别处于不同的线程。对于这样的多线程协作,正好可以使用 Guarded Suspension 模式。

场景二

Dubbo 的调用是异步的,却可以得到同步的返回结果。这也是经典的异步转同步的方法。翻阅 Dubbo 的 DefaultFeture 类,我们可以看到它的源码也使用了 Guarded Suspension 模式。

Part2Guarded Suspension 模式的使用

3通用的 Guarded Suspension 模式

基于上述的说明,我们创建一个通用的受保护对象 GuardedObject,这里用到了 Lock、Condition 来实现该对象。当然,用 Java 的 wait() 和 notify()/notifyAll() 方法也是可以的。

public class GuardedObject<K,V> {

    private static final int TIMEOUT = 10;
    private static final Map<Object, GuardedObject> goMap = new ConcurrentHashMap<>();

    // 受保护对象
    private V obj;
    private final Lock lock = new ReentrantLock();
    private final Condition done = lock.newCondition();

    public static <K,V> GuardedObject create(K key) {
        GuardedObject go = new GuardedObject<K,V>();
        goMap.put(key, go);
        return go;
    }

    // 唤醒等待的线程
    public static <K, V> void fireEvent(K key, V obj) {
        GuardedObject go = goMap.remove(key);
        if (go != null) {
            go.onChange(obj);
        }
    }

    // 获取受保护对象
    public V get(Predicate<V> p) throws WatcherException{
        lock.lock();
        try {
            while (!p.test(obj)) {
                done.await(TIMEOUT, TimeUnit.SECONDS);
            }
        } catch (InterruptedException e) {
            throw new WatcherException(e);
        } finally {
            lock.unlock();
        }
        return obj;
    }

    // 事件通知方法
    public void onChange(V obj) {
        lock.lock();
        try {
            this.obj = obj;
            done.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

在 GuardedObject 对象中, goMap 是静态变量,所以这个 Map 存储了所有的 GuardedObject 对象。

另外,WatcherException 是我们业务异常,在这里可以替换成 RuntimeException。

我们用代码来模拟 Guarded Suspension 模式的使用过程。

class SomeObj{

}

public class Test {

    public static void main(String[] args) {

        try {
            GuardedObject<Integer,SomeObj> guardedObject = GuardedObject.create(1);

            Thread t1 = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "线程开始访问受保护对象");
                guardedObject.get(e -> {
                    return e!=null;
                });
                System.out.println(Thread.currentThread().getName() + "线程访问到了受保护对象");
            });
            t1.setName("t1");
            t1.start();

            Thread t2 = new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "线程开始做准备的工作");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                GuardedObject.fireEvent(1,new SomeObj());
                System.out.println(Thread.currentThread().getName() + "线程准备工作完成,条件满足,唤醒等待的线程");
            });
            t2.setName("t2");
            t2.start();

            t1.join();
            t2.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

t1线程开始访问受保护对象
t2线程开始做准备的工作
t2线程准备工作完成,条件满足,唤醒等待的线程
t1线程访问到了受保护对象

4支持超时机制的 Guarded Suspension 模式

对于将异步调用转化成同步调用的操作时,肯定是需要超时机制的。因此,将上述代码修改一下,增加了超时机制。

public class GuardedObject<K,V> {

    private static final int TIMEOUT = 10;
    private static final Map<Object, GuardedObject> goMap = new ConcurrentHashMap<>();

    // 受保护对象
    private V obj;
    private final Lock lock = new ReentrantLock();
    private final Condition done = lock.newCondition();

    public static <K,V> GuardedObject create(K key) {
        GuardedObject go = new GuardedObject<K,V>();
        goMap.put(key, go);
        return go;
    }

    /**
     * 判断条件
     * @return
     */
    private boolean isArrived() {
        return obj != null;
    }

    /**
     * 唤醒等待的线程
     * @param key
     * @param obj
     */
    public static <K, V> void fireEvent(K key, V obj) {
        GuardedObject go = goMap.remove(key);
        if (go != null) {
            go.onChange(obj);
        }
    }

    /**
     * 获取受保护对象
     * @return
     * @throws WatcherException
     */
    public V get() throws WatcherException{
        return get(TIMEOUT);
    }

    /**
     * 获取受保护对象
     * @return
     * @throws WatcherException
     */
    public V get(long timeout) throws WatcherException{
        lock.lock();
        try {
            while (!isArrived()) {
                //等待,增加超时机制
                try {
                    done.await(timeout, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    throw new WatcherException(e);
                }

                if (!isArrived()) {
                    throw new WatcherException("timeout");
                }
            }
        } finally {
            lock.unlock();
        }
        return obj;
    }

    /**
     * 事件通知方法
     * @param obj
     */
    private void onChange(V obj) {
        lock.lock();
        try {
            this.obj = obj;
            done.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

如果超时的话,GuardedObject#get() 会抛出异常,在调用时捕获异常就可以了。

Part3总结

笔者正好使用该模式,将某个串口调用的第三方库 (https://github.com/NeuronRobotics/nrjavaserial) 从原先只支持异步调用,改成了也可以支持同步调用,增加了超时的机制,并应用于生产环境中。

对于多线程的协作,当然还有其他方式。比如 A 线程轮询等待 B 线程结束后,再去执行 A 线程的任务。对于这种情况,肯定是使用 Guarded Suspension 模式更佳。或者通过 eventbus 这样的事件总线来实现多线程的协作。

Java与Android技术栈】公众号

关注 Java/Kotlin 服务端、桌面端 、Android 、机器学习、端侧智能

更多精彩内容请关注:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 多线程模式 —— Guarded Suspension 模式 的相关文章

  • Android Studio 1.0.1 APK META-INF/DEPENDENCIES 中复制的重复文件

    我安装了 Android Studio 版本 1 0 1 并尝试将我的项目从 eclipse 导入到它 它给了我以下错误 Error Execution failed for task app packageDebug Duplicate
  • Spring - 使用 new 是一种不好的做法吗?

    正在创建对象by hand 即使用new操作员而不是注册Springbean 和使用依赖注入被认为是不好的做法吗 我的意思是 确实Spring IoC容器必须了解应用程序中的所有对象吗 如果是这样 为什么 你希望 Spring 创建 bea
  • 通常可重用的注释或公共注释?

    有没有常用的注释 类似于 commons lang 如果没有 您是否见过在任何开源应用程序开发中有效使用注释 不是内置注释 的情况 我记得 Mifos 用它来进行交易 Mohan i think 休眠验证器 http www hiberna
  • 将 JSON 与嵌套数组和 json 进行比较(数组顺序无关紧要)

    你好 我正在尝试比较java中的两个json 每个键可以包含一个json对象或json对象数组 并且它们中的每个也可以是数组或json 这是 Json 的示例 id 123123asd123 attributes name apps val
  • 如何在java中将ojalgo稀疏数组存储到文件中?

    我目前有一个 SparseStore 矩阵 我在其中执行大量计数和计算 我想将其存储到文件中 以便以后可以重复使用它 而无需重新执行之前的所有计算 我尝试了 Java 中的基本序列化 ObjectOutputStream outputStr
  • 在 Android 中 - 如何使用 ClickableSpan 只注册长点击

    我想注册对包含在 ClickableSpan 中的文本的点击 前提是点击时间超过 1 秒 有什么办法可以做到这一点吗 如果没有 捕获双击也可以 如果 onClick 方法捕获了一个包含有关点击的一些元数据的事件 那就太好了 那么如果点击长度
  • 可以将矩形设置为显示边框吗?

    以下应用 public class Temp extends Application Override public void start Stage primaryStage StackPane root new StackPane Re
  • 如何获取eclipse中的工作空间路径? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我正在研究PDE Eclipse Plugin Project 我需要获取工作区路径 我的文本小部件 swt 应该设置为当前工作空间路径 如
  • 在可序列化 Java 类中使用记录器的正确方法是什么?

    我有以下 doctored 我正在开发的系统中的类以及Findbugs http findbugs sourceforge net 正在生成一个SE BAD FIELD http findbugs sourceforge net bugDe
  • Android Camera2 API 预览有时会失真

    我正在使用 Camera2 API 构建自定义相机 到目前为止 除了预览有时会失真之外 相机工作得很好 假设我连续打开相机 7 次 所有尝试均成功 但第 8 次相机预览失真 看起来它使用宽度作为高度 反之亦然 我的代码基于camera2的G
  • 在 Spring 中以编程方式解析 AliasFor 注释值

    我有一个注释 Target ElementType TYPE Retention RetentionPolicy RUNTIME public interface A Class value 这是在课堂上使用的 B D class publ
  • Android Studio:通过命令行安装Release App

    我想从命令行导出 apk 文件 release apk 当我运行这个命令时 gradlew installRelease 我发现了这个 在根项目 中找不到 Android Studio 任务 安装版本 一些候选者是 卸载版本 我该如何解决
  • 使用 appWidgetId 检查主屏幕上是否存在小部件

    我在用AlarmManager更新我的小部件 如果主屏幕上没有小部件 我想停止它 但我在检测主屏幕上是否没有小部件时遇到问题 每当我尝试使用这种方式获取 AppWidgetIds 时 AppWidgetManager appWidgetMa
  • Java 8 中接口和抽象类之间的根本区别[重复]

    这个问题在这里已经有答案了 考虑到接口现在可以为其提供的方法提供实现 我无法正确合理地解释接口和抽象类之间的差异 有谁知道如何正确解释其中的差异 我还被告知 从性能角度来看 接口比抽象类更轻量 有人可以证实这一点吗 接口仍然不能有任何状态
  • 主屏幕活动快捷方式

    我有 2 项活动 1 主要活动 2 第二个活动 我希望创建第二个活动的主屏幕快捷方式 Intent shortcutIntent new Intent getApplicationContext SecondActivity class s
  • Java 压缩字符串

    我需要创建一个接收字符串并返回字符串的方法 防爆输入 AAABBBCCC 防爆输出 3A4B2C 好吧 这很尴尬 我在今天的面试中无法做到这一点 我正在申请初级职位 现在 我在家尝试制作一些静态工作的东西 我的意思是 不使用循环有点无用 但
  • 使用本地 SQlite 数据库填充可扩展列表视图的方法

    我的应用程序中有一个 sqlite 数据库 我想用它制作一个可扩展的列表视图 我已经确定了我应该采取的方法 尝试了很多方法来找到相同的教程 但找不到一个使用本地数据库填充可扩展列表的教程 Android 网站上有一个教程 他们使用手机中的联
  • Android Map API V2 设置自定义 InfoWindow 位置

    我对地图上的所有标记使用 InfoWindowAdapter Api v2 所有标记都清晰可见 问题是我的自定义信息窗口的大小约为 500px 300px 当我触摸地图上的任何点时 它被设置为屏幕中心 因此信息窗口将从顶部裁剪 我的要求是根
  • Java邮件,设置回复地址不起作用

    我用java写了一个小的电子邮件发送程序 它有from to and reply to地址 当客户端尝试回复邮件时 应该能够回复reply to地址 目前它不起作用 我的代码如下 File Name SendEmail java impor
  • Android 应用程序还需要包含扩展文件下载器吗?

    我的问题是当应用程序大于 50MB 并且必须使用它们时 应用程序是否需要能够下载扩展文件 根据 Android 开发人员帮助 应用程序可以依赖扩展文件 在较新的设备上 扩展文件会在 APK 之前 之后自动下载 而在 较旧的设备 上 开发人员

随机推荐

  • PicoNeo3开发VR——小白教程

    不断更新中 欢迎大佬们来指导 纠错 导入PicoVRSDK 1 新创一个Unity工程 Unity版本最好选择2019 4以上版本 以及需配置好安卓环境 然后导入官方picoVRSDK 2 渲染设置 Graphics APIs暂不支持Vul
  • DirectShow系列讲座之二——Filter原理

    在上一讲中 笔者介绍了DirectShow的总体系统框架 从这一讲开始 我们要从程序员的角度 进一步深入探讨一下DirectShow的应用以及Filter的开发 在这之前 笔者首先要特别提一下微软提供的一个Filter测试工具 GraphE
  • SpringBoot 高级进阶

    目录标题 SpringBoot 高级 1 RabbitMQ概述 1 RabbitMQ简介 消息服务中两个重要概念 消息队列主要有两种形式的目的地 异步处理 应用解耦 流量削峰 核心概念 2 RabbitMQ运行机制 3 RabbitMQ安装
  • 分布式部署 Zabbix 监控平台

    分布式部署 Zabbix 监控平台 一 基本介绍 二 部署 LNMP 架构 1 配置 MySQL 服务 2 配置 Nginx PHP 服务 1 安装 Nginx 2 安装 PHP 3 修改 PHP 配置文件 4 启动脚本 三 部署 Zabb
  • 跨站脚本攻击XSS(最全最细致的靶场实战)

    一 XSS跨站漏洞 1 XSS简介 网站中包含大量的动态内容以提高用户体验 比过去要复杂得多 所谓动态内容 就是根据用户环境和需要 Web应用程序能够输出相应的内容 动态站点会受到一种名为 跨站脚本攻击 Cross Site Scripti
  • 教你如何快速下载python

    1 打开python官网 链接 py官网 2 找到自己要安装的版本 点击下载 3 下载 exe文件 打开 4 弹出标题为Install Python X X X X bit 的窗口 将下面的Add Python 打勾 点Install No
  • (一)软件架构概述

    1 系统结构 B S架构 Browser Server 浏览器 服务器的交互形式 Browser支持哪些语言 HTML CSS JavaScript 写HTML CSS JavaScript代码的这波人职位叫做 WEB 前端开发工程师 Ja
  • Python学习笔记(四)

    文章目录 1 进程 1 1 系统原生 OS 模块 创建进程 1 2 multiprocessing 模块 1 2 1 Process 单进程 1 2 2 Pool 进程池 1 3 subprocess 模块 使用外部子进程 2 线程 2 1
  • 爬虫 跨域请求 获取json数据 解决参数加密

    分析网址 提示 抓取对方信息是通过对方允许的 请不要违法操作 抓取其他个人有关信息 网址先发送了一个OPTIONS请求 Request URL http xxxxxxxx com Request Method OPTIONS Status
  • C++知识积累:内存对齐理解

    为什么要进行内存对齐 这是因为CPU的读取总是对齐的 举个例子 假设CPU是32位的 那么CPU每次读取的4字节数据的首地址都是4的倍数 也就是说 内存中数据首地址为4的倍数时 CPU一次操作就可以完成数据读取 假设有一个int型四字节大小
  • 警告: Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassD

    说明 主要参考了这位大佬的文章 https www cnblogs com xxjcai p java compiler html 不过我是在使用IDEA的时候遇到的问题 所以我这里介绍一下在IDEA中的解决方案 错误信息 src main
  • VS2017 NuGet包管理

    一 在 https www nuget org 注册账号并生成APIKEY 二 在命令行窗口启动nuget exe 三 生成 nuspec文件 将nuget exe放置在项目目录 注意下图 处的id version 打包后会生成 id ve
  • C/C++ int a[]和int (*a)[]的区别

    int a a与 先结合 a为数组 数组element type int int a a与 先结合 a为指针 指向数组 同int a link https www jianshu com p 548ff8e1b243
  • CentOS7修改SSH端口

    CentOS7 修改SSH端口 文章目录 CentOS7 修改SSH端口 1 修改ssh配置文件 1 1 查看默认端口 1 2 修改端口 2 防火墙放行 2 1 查看防火墙状态 2 2 防火墙放行端口 202 2 3 查看已开启端口 2 4
  • svn客户端检出的工程导入eclipse后不显示SVN信息

    1 首先确定原因 是由于SVN客户端与SVN插件版本不对应导致的 因此需要更换SVN插件版本 1 1 SVN插件与SVN客户端版本对应关系 插件svn1 4 x对应TortoiseSvn 1 5 x 插件svn1 6 x对应Tortoise
  • Keil MDK编程环境下的 STM32 IAP下载(学习笔记)

    IAP下载 IAP的引入 不同的程序下载方式 ICP ICP In Circuit Programing 在电路编程 可通过 CPU 的 Debug Access Port 烧录代码 比如 ARM Cortex 的 Debug Interf
  • Jsp页面java.lang.NumberFormatException: For input string: ““错误解决办法

    Jsp页面报Java lang NumberFormatException For input string 错误解决办法 昨天写代码遇到一个错误 在日志和控制台报一个错误说jsp页面一个出现java lang NumberFormatEx
  • React-Router V6 使用详解

    一 基本用法 React Router的安装方法 npm npm install react router dom 6 yarn yarn add react router dom 6 目前官方从5开始已经放弃原有的react router
  • Java中Date日期处理类,Calendar日期类,SimpleDateFormat

    1 date 获取当前系统时间 date getTime 获取系统时间毫秒值 package import java util Date public class DateDemo public static void main Strin
  • Java 多线程模式 —— Guarded Suspension 模式

    Part1Guarded Suspension 模式的介绍 我们只从字面上看 Guarded Suspension 是受保护暂停的意思 1Guarded Suspension 模式 在实际的并发编程中 Guarded Suspension