多线程教程(十二)同步模式之保护性暂停

剑指大厂,手撕 Java 八股文 java 知识的汇总贴,包含八股文。知识讲解详细。 tip: 此贴为目录贴,定期更新toNew: 时间是最好的答案,它能解决所有问题。坚持!!! 阅读详情

多线程教程(十二)同步模式之保护性暂停

一、定义

保护性暂停,即 Guarded Suspension,用在一个线程等待另一个线程的执行结果

要点

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject

如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)

JDK 中,join 的实现、Future 的实现,采用的就是此模式

因为要等待另一方的结果,因此归类到同步模式

在这里插入图片描述

二、实现
class GuardedObject {

    private Object response;
    private final Object lock = new Object();

    public Object get() {
        synchronized (lock) {
            // 条件不满足则等待
            while (response == null) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

    public void complete(Object response) {
        synchronized (lock) {
            // 条件满足,通知等待线程
            this.response = response;
            lock.notifyAll();
        }
    }
}

调用

public static void main(String[] args) {
    GuardedObject guardedObject = new GuardedObject();
    new Thread(() -> {
        try {
            List<String> response = download();
            log.debug("download complete...");
            guardedObject.complete(response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();

    log.debug("waiting...");
    Object response = guardedObject.get();
    log.debug("get response: [{}] lines", ((List<String>) response).size());

}

执行结果

08:42:18.568 [main] c.TestGuardedObject - waiting...
08:42:23.312 [Thread-0] c.TestGuardedObject - download complete...
08:42:23.312 [main] c.TestGuardedObject - get response: [3] lines
三、带超时版GuardedObject
/**
 * 添加超时处理
 */
@Slf4j(topic = "c.GuardedObjectV2")
class GuardedObjectV2 {

    private Object response;
    private final Object lock = new Object();

    public Object get(long millis) {
        synchronized (lock) {
            // 1) 记录最初时间
            long last = System.currentTimeMillis();
            // 2) 已经经历的时间
            long timePassed = 0;
            while (response == null) {
                // 4) 假设 millis 是 1000,结果在 400 时唤醒了,那么还有 600 要等
                long waitTime = millis - timePassed;
                log.debug("waitTime: {}", waitTime);
                if (waitTime <= 0) {
                    log.debug("break...");
                    break;
                }
                try {
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 3) 如果提前被唤醒,这时已经经历的时间假设为 400
                timePassed = System.currentTimeMillis() - last;
                log.debug("timePassed: {}, object is null {}", timePassed, response == null);
            }
            return response;
        }
    }

    public void complete(Object response) {
        synchronized (lock) {
            // 条件满足,通知等待线程
            this.response = response;
            log.debug("notify...");
            lock.notifyAll();
        }
    }
}

使用while让线程一直等待。等待时间到了之后用break打破while循环。

long waitTime = millis - timePassed;

用waitTime来防止wait被其他线程唤醒但不满足后续执行的条件,再次睡眠导致wait时间超过设定时间

调用

public static void main(String[] args) {
    GuardedObjectV2 v2 = new GuardedObjectV2();
    new Thread(() -> {
        sleep(1);
        v2.complete(null);
        sleep(1);
        v2.complete(Arrays.asList("a", "b", "c"));
    }).start();

    Object response = v2.get(2500);
    if (response != null) {
        log.debug("get response: [{}] lines", ((List<String>) response).size());
    } else {
        log.debug("can't get response");
    }
}

输出

08:49:39.917 [main] c.GuardedObjectV2 - waitTime: 2500
08:49:40.917 [Thread-0] c.GuardedObjectV2 - notify...
08:49:40.917 [main] c.GuardedObjectV2 - timePassed: 1003, object is null true
08:49:40.917 [main] c.GuardedObjectV2 - waitTime: 1497
08:49:41.918 [Thread-0] c.GuardedObjectV2 - notify...
08:49:41.918 [main] c.GuardedObjectV2 - timePassed: 2004, object is null false
08:49:41.918 [main] c.TestGuardedObjectV2 - get response: [3] lines

测试超时:

// 等待时间不足
List<String> lines = v2.get(1500);

输出:

08:47:54.963 [main] c.GuardedObjectV2 - waitTime: 1500
08:47:55.963 [Thread-0] c.GuardedObjectV2 - notify...
08:47:55.963 [main] c.GuardedObjectV2 - timePassed: 1002, object is null true
08:47:55.963 [main] c.GuardedObjectV2 - waitTime: 498
08:47:56.461 [main] c.GuardedObjectV2 - timePassed: 1500, object is null true
08:47:56.461 [main] c.GuardedObjectV2 - waitTime: 0
08:47:56.461 [main] c.GuardedObjectV2 - break...
08:47:56.461 [main] c.TestGuardedObjectV2 - can't get response
08:47:56.963 [Thread-0] c.GuardedObjectV2 - notify...
四、多任务版GuardedObject

图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右侧的 t1,t3,t5 就好比邮递员

如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理

在这里插入图片描述

解决方案实际上就是创建多个锁实体,每个唤醒线程用指定锁实体的唤醒线程

1.创建保护实体

class GuardedObject {

    // 标识 Guarded Object
    private int id;

    public GuardedObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    // 结果
    private Object response;

    // 获取结果
    // timeout 表示要等待多久 2000
    public Object get(long timeout) {
        synchronized (this) {
            // 开始时间 15:00:00
            long begin = System.currentTimeMillis();
            // 经历的时间
            long passedTime = 0;
            while (response == null) {
                // 这一轮循环应该等待的时间
                long waitTime = timeout - passedTime;
                // 经历的时间超过了最大等待时间时,退出循环
                if (timeout - passedTime <= 0) {
                    break;
                }
                try {
                    this.wait(waitTime); // 虚假唤醒 15:00:01
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 求得经历时间
                passedTime = System.currentTimeMillis() - begin; // 15:00:02  1s
            }
            return response;
        }
    }

    // 产生结果
    public void complete(Object response) {
        synchronized (this) {
            // 给结果成员变量赋值
            this.response = response;
            this.notifyAll();
        }
    }
}

该保护实体添加了id,这样能够保证多个保护实体能够不同区分。

get方法还是获取response,complete方法给response赋值,并唤醒wait线程,满足get方法继续执行的条件

  1. 创建中间解耦类
class Mailboxes {
    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();

    private static int id = 1;
    // 产生唯一 id
    private static synchronized int generateId() {
        return id++;
    }

    public static GuardedObject getGuardedObject(int id) {
        return boxes.remove(id);
    }

    public static GuardedObject createGuardedObject() {
        GuardedObject go = new GuardedObject(generateId());
        boxes.put(go.getId(), go);
        return go;
    }

    public static Set<Integer> getIds() {
        return boxes.keySet();
    }
}

Mailboxes可以看作图例中的信箱,也可以看作一个工厂,根据id创建保护实体。

同时提供了获取实体,不过用的remove方法,只能一次获取,获取后map不在包含获取的守护实体。

  1. 创建业务模拟类

收件人类

@Slf4j(topic = "c.People")
class People extends Thread{
    @Override
    public void run() {
        // 收信
        GuardedObject guardedObject = Mailboxes.createGuardedObject();
        log.debug("开始收信 id:{}", guardedObject.getId());
        Object mail = guardedObject.get(5000);
        log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
    }
}

送件人类

@Slf4j(topic = "c.Postman")
class Postman extends Thread {
    private int id;
    private String mail;

    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
        log.debug("送信 id:{}, 内容:{}", id, mail);
        guardedObject.complete(mail);
    }
}
  1. 测试
@Slf4j(topic = "c.Test20")
public class Test20 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }
        Sleeper.sleep(1);
        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "内容" + id).start();
        }
    }
}
  1. 输出
10:35:05.689 c.People [Thread-1] - 开始收信 id:3
10:35:05.689 c.People [Thread-2] - 开始收信 id:1
10:35:05.689 c.People [Thread-0] - 开始收信 id:2
10:35:06.688 c.Postman [Thread-4] - 送信 id:2, 内容:内容2
10:35:06.688 c.Postman [Thread-5] - 送信 id:1, 内容:内容1
10:35:06.688 c.People [Thread-0] - 收到信 id:2, 内容:内容2
10:35:06.688 c.People [Thread-2] - 收到信 id:1, 内容:内容1
10:35:06.688 c.Postman [Thread-3] - 送信 id:3, 内容:内容3
10:35:06.689 c.People [Thread-1] - 收到信 id:3, 内容:内容3
学习《Java并发编程》目录索引 (持续更新中)      写在前面:此系列为本人学习黑马《Java并发编程》所整理的笔记 ! 不局限于应用层开发, 更加深入理解多线程, 并发编程, 更有利于我们长久发展 ! 学习Java并发编程 阅读详情

相关推荐

手把手教你用srec_cat生成带CRC校验的STM32固件(解决OTA常见校验失败问题)

本文针对STM32 OTA升级中常见的固件校验失败问题,提供了一套使用srec_cat工具自动生成带CRC32校验固件的实战解决方案。文章详细拆解了命令参数,指导开发者如何为固件嵌入CRC值、处理地址偏移及合并文件,从而确保Bootloader能可靠校验固件完整性,提升远程更新的稳定性。

kappa的博客 783

同步模式之保护性暂停模式

本文介绍了保护性暂停模式,常用在一个线程需要等待另一个线程的结果或者执行结束的场景,JDK 中,join()、Future 的实现用的都是保护性暂停模式,通过对保护性暂停的两种拓展简单介绍了 join 和 Future 的原理

m0_74059961的博客 776

噪声整形与过采样实战:从N-bit ADC到DSM的SQNR优化指南

本文深入探讨了噪声整形与过采样技术在DSM(Delta-Sigma调制器)中的应用,重点分析了量化器位数(N)、滤波器阶数(L)和过采样率(OSR)对SQNR优化的影响。通过实战案例和参数协同优化策略,帮助工程师在音频处理和通信系统设计中实现更优的信号质量。

weixin_42561040的博客 339

JUC/多线程 模式(四)

即,用在一个线程等待另一个线程的执行结果产生结果的线程和使用结果的线程是一一对应的,有多少个生产结果的线程就有多少个使用结果的线程。要点有一个结果需要从一个线程传递到另一个线程,让他们关联同一个如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者消费者)JDK中,join的实现、Future的实现,采用的就是此模式因为要等待另一方的结果,因此归类到同步模式。

一+一加1的博客 1100

Kindle电子书DRM破解全攻略:从下载到转换的完整流程(含最新DeDRM插件配置)

本文详细介绍了如何合法移除个人购买的Kindle电子书的DRM保护,实现跨设备阅读与备份。核心内容包括使用最新DeDRM插件(如noDRM分支)配合Calibre进行配置,并针对亚马逊最新的KFX加密格式提供了密钥提取与实战操作指南。文章强调该方法仅适用于个人合理使用,旨在帮助读者真正掌控自己的数字资产。

delta的博客 575

Unity半透明物体景深失效?教你用双材质球方案完美解决(附完整Shader代码)

本文深入剖析了Unity中半透明物体导致景深效果失效的根本原因,即半透明物体默认不写入摄像机深度图。针对此问题,文章详细介绍了稳定高效的“双材质球”解决方案,通过为物体附加一个仅写入深度信息的不可见材质,完美修复景深效果,并提供了完整的Shader代码与实现脚本。

weixin_29010003的博客 381

多线程设计模式-保护性暂停模式

有这么一个场景,我们有两个线程,其中一个线程需要等待获取另外一个线程的执行结果,之气那我们是使用join方法可以实现,现在我们不用jon来实现。同时我们可以看一下join方法的底层实现原理,这里面也用到了超时等待,我们上面所写的就是join超时等待的实现原理。上面都是单任务的情况,我们还可以对上面的代码进行修改,改成多任务的版本。并创建一个公共类用来存放GuardedObject 类。同时然后编写业务类,分别添加任务和获取执行的结果。在GuradeObject类中加入以下代码。最后编写代码进行测试。

怕什么真理无穷,进一寸有一寸的欢喜。即使开了一辆老掉牙的破车,只要在前行就好,偶尔吹点小风,这就是幸 305

双击文件夹弹窗“找不到应用程序”,但是右键点击打开可以

此电脑、回收站、c、d、f盘、控制面板、任意文件夹等都没有办法双击打开。 右键中包含“使用filemanager打开”,是由于FileManager卸载残留导致的。 懒人专用附件 下载解压上面附件,双击导入可以清理FileManager软件残留 参考: 双击此电脑出现找不到应用程序,但是右键点击可以-其他-优快云问答https://ask.youkuaiyun.com/questions/7631439 电脑上的文件夹双击都提示找不到应用程序,但是右击打开就能打开,该怎么解决? - 知乎 (z

webmasterqpwou的博客 1万+

并发编程学习(五):设计模式~同步模式之保护性暂停

保护性暂停 即Guarded Suspension,用于在一个线程等待另一个线程的执行结果。要点:有一个结果需要从一个线程传递到另一个线程,让它们关联同一个对象GuardedObject。如果有结果不断从一个线程到另一个线程,那么可以使用消息队列。JDK中,join的实现、Future的实现,采用的就是此模式。

weixin_40482816的博客 1199

在 Dify 平台部署一个 知识库问答(KBQA) 工作流

加强检索约束,Prompt 加限制(“仅基于知识库回答”):启用缓存,使用轻量级 LLM(如 Mistral-7B)(OpenAI Embedding、M3E、BGE等)(支持 PDF/TXT/Markdown/Word)(GPU/CPU 资源,用于向量数据库和模型推理)企业内部文档(PDF/Word/Excel)网页爬取数据(Markdown/HTML)分块大小(太小→信息碎片化,太大→噪声多)相似度阈值(太高→漏检,太低→噪声多)(是否返回正确文档)结构化数据(数据库/API)

井底小蛙的博客 1519

YOLOv5源码逐行超详细注释与解读(1)——项目目录结构解析

全网最全YOLOv5项目目录结构超详细分析。逐个文件注释,小白上手必备

路人贾的博客 9万+

Java并发编程之设计模式

同步模式之保护性暂停 1. 定义 即 Guarded Suspension,用在一个线程等待另一个线程的执行结果 要点 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者) JDK 中,join 的实现、Future 的实现,采用的就是此模式 因为要等待另一方的结果,因此归类到同步模式 2. 实现 应用 一个线程等待另一个线程的执行结果 执行结果 3. 带超时版 GuardedObj

qq_42964601的博客 241

LAMMPS后处理—如何构建二维云图-接触角呢?

计算接触角

Rockyoung 6521

多线程十二种设计模式详解

文章目录前言多线程程序的评价标准Single Threaded Execution 模式Single Threaded Execution 模式简介Single Threaded Execution 模式中的登场角色在什么情况下使用 Single Threaded Execution 呢 ?生存性与死锁可复用性和继承反常临界区的大小和性能Immutable Object(不可变对象) 模式Immutable Object模式简介示例代码Immutable 模式中的登场角色在什么场景下使用Immutable模

qq_42605751的博客 2786

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Trae Agent 旧版 JSON 配置指南:字段详解、源码实现与迁移到 YAML 的完整方案

> 本文以 [docs/legacy_config.md](https://link.gitcode.com/i/dd6b7a71c6628f4730582d600e5ed6f4) 为骨架,围绕 Trae Agent 的旧版 JSON 配置格式展开:先讲解 JSON 配置的安装与字段结构,再从源码层面剖析 `LegacyConfig` 的解析逻辑与默认值、配置优先级、环境变量覆盖机制,最后给出从

gitblog_00505的博客 481

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

GitHub爆热门!最全并发编程合集上线3分钟获星标180K

借用Java并发编程实践中的话"编写正确的程序并不容易,而编写正常的并发程序就更难了",相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,下面算是对多线程情况下同步策略的一个简单介绍。ConcurrentLinkedQueue原理。LinkedBlockingQueue原理。ConcurrentHashMap原理。ReentrantLock原理。syncronized原理。终止模式之两阶段终止模式。volatile原理。

2401_89758248的博客 396

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Java并发(二十四)----wait、notify、notifyAll、join区别与联系

当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;3、调用wait()后必须调用notify()或notifyAll()后线程才会从等待池进入到锁池,当我们的线程竞争得到同步锁后就会重新进入绪状态等待cpu资源分配。如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到join结束或中断线程B才开始进入阻塞队列。notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

601

遇到并发就发怵?这份GitHub标星180k的JAVA并发编程合集有奇效,还不赶紧来学!

并发一定比串行快么? 这个问题肯定是错的,并发比串行慢的原因在于:线程有创建和上下文切换的开销

老男孩的架构路 234

GitHub爆热门 最全并发编程合集上线3分钟获星标180K

私信(学习)获取高清大纲。

jjc4261的博客 140

JUC笔记-共享模型之管程 (Monitor)

JUC-共享模型之管程( Monitor)一、线程安全问题(重点)1.1 同步1.2 线程出现问题的根本原因分析1.3 synchronized 解决方案1.3.1 同步代码块1.3.2 synchronized原理1.3.3 同步方法二、线程八锁2.1 锁住同一个对象都是this(e1对象),结果为:1,2或者2,12.2 锁住同一个对象都是this(e1对象),结果为:1s后1,2 || 2,1s后12.3 a,b 锁住同一个对象都是this(e1对象), c没有上锁。结果为:3,1s后1,2 ||

weixin_50255400的博客 1710

剑指大厂,手撕 Java 八股文_concurrenthashmap底层原理

最全的Linux教程,Linux从入门到精通。

2401_84254057的博客 800

终于明白为什么面试官老拿并发编程为难程序员了,这份Github上八月份点赞近70.3k的并发编程笔记+Xmind实在太香了

今天想把代码世界比较难的部分并发编程讲给大家听,提到并发编程,各种锁不得不提,他们难舍难分。 你听说过的锁有什么?互斥锁、自旋锁、偏向锁、轻量级锁、重量级锁、公平/非公平锁,读写锁,可重入锁......是不是已经很烦了,不过看完今天这篇,这些名词都和1+1一样简单。 还是强调一下,用“面向思想”的方式去学习技术,事半功倍。 并发编程的好处大家再清楚不过了,提高性能,增加吞吐量......可对于初级程序员来讲,很难接触到,甚至某些工作两三年的,如果面试你问是不是有并发编程的经验时,他们也只会回答说.

如果频繁就私信一下呢 346
上一篇: 多线程教程(十一)waitnotify的正确使用
下一篇: 多线程教程(十三) 设计模式之balking(犹豫模式)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值