写点什么

【C 语言难点突破】动态内存知识详解

作者:Geek_65222d
  • 2022 年 10 月 07 日
    河南
  • 本文字数:3253 字

    阅读完需:约 11 分钟


join 的实现原理代码示例

源码分析

我们可以看出如果 join 给定的时间小于 0 就抛出异常

如果给定的时间为 0,其实也就是不加参数的 join,他就会判断是否 isAlive 也就是判断调用 join 的线程是否存活,如果存活则 wait(0)即一直等待,注意这里的 wait 是让 t1.join 同步的线程等待 t1 线程运行。举一个例子就是在 main 线程运行 t1.join(),那么 isAlive 判断的是 t1 线程是否存在,wait(0)是让 main 线程进行等待

如果给定的时间大于 0,那么这里的代码就和我们的上一篇内容里的保护性暂停扩展-增加超时 一模一样了,这里就是 保护性暂停 这个设计模式的体现


join 方法整体就是 保护性暂停 设计模式 的体现,只不过它没有传递数据 只是用来让其他线程陷入等待 使其调用 join 的线程可以先执行

扩展方面 解耦等待与成产

基本介绍


这个图中 t0 t2 t4 是寄信人线程,t1 t3 t5 负责邮递员线程 负责送信与代写信。每个信都有自己的 id 与内容 id 可以理解为收信人的地址,寄信人通过邮递员把新送给收信人,这里我们只研究寄信人与邮递员之间的消息传递。注意:一个邮递员只能寄一封信。

为什么要这样设计

这样设计的好处是解耦 也就是降低消息传递的复杂度与关联度,我们把 Futures 当做邮局 寄信人 把信给邮局 然后邮递员来取信送出,我们可以想象一下 如果没有这个邮局 我们会怎么样?我们需要亲自跑到邮递员家里 把信给他 这期间邮递员可能有事出门了 你就白去了 总之这样做 效率很低,这就是邮局的好处。

代码示例

@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();        }    }}
@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); }}
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(); }}
// 增加超时效果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(); } }}
复制代码


结果:


12:21:51.157 c.People [Thread-1] - 开始寄信 id:312:21:51.158 c.People [Thread-0] - 开始寄信 id:112:21:51.157 c.People [Thread-2] - 开始寄信 id:212:21:52.171 c.Postman [Thread-4] - 送信 id:2, 内容:内容 212:21:52.171 c.Postman [Thread-5] - 送信 id:1, 内容:内容 112:21:52.171 c.Postman [Thread-3] - 送信 id:3, 内容:内容 312:21:52.171 c.People [Thread-0] - 寄信的内容 id:1, 内容:内容 112:21:52.171 c.People [Thread-2] - 寄信的内容 id:2, 内容:内容 212:21:52.171 c.People [Thread-1] - 寄信的内容 id:3, 内容:内容 3


解释与分析:


我们可以看出 我们寄信后每个信 都有一个对应的 邮递员来送信 信的内容与 id 也一致


我们首先关注到了几个类分别是:GuardedObject


GuardedObject 类是写入消息与获取消息的类,由于信是邮递员代写 所以邮递员写信就是写入消息 收信人得到信就是获取消息这个类有几个方法:getId:获取这个信的 idget:获取这个信,有时限的等待,如果获取时间过长就放弃获取 complete:写信


Mailboxes


Mailboxes 类 可以理解为邮局 负责解耦寄信人与邮递员,主要形式是通过 id 与信之间的一一对应实现,邮递员不需要知道寄信的是谁 他只需要关注他要寄到的地址(就是信的 id)是什么。这个类有几个方法与成员变量:boxes 变量:是一个 Hashtable 类型 因为 Hashtable 是线程安全的 所以不用考虑关于 用到有关 Hashtable 的方法的线程安全问题,Hashtable 的 key 是 id value 是信,我们通过 id 找到信 generateId 方法:作用是生产信的 id,在 createGuardedObject 创建信时被调用 getGuardedObject 方法:作用是邮递员通过 id 获取信 然后邮递员送信 因为已经送信了 所以我们需要把它的 Hashtable 删除,所以我们这里选择使用的方法是 boxes.remove(id); 通过 remove 获取信 并且删除它的 HashtablecreateGuardedObject 方法:作用是创建一封信 也就是一个 Hashtable,可以理解为寄信人 要寄信 需要先创建一个信封并且在信封上写上地址(id)getIds 方法:这个方法的作用是获取所有 id,目的是 让邮递员选择一个 id 对应的信送出


Postman


Postman 线程类 是邮递员类,作用是送信和代写信这个类有几个方法与成员变量:id 变量:要送信的 idmail 变量:要送信的内容 Postman:有参构造,参数为 id mail,通过 id 获取到信(内容为空) 然后往其中写入内容 mailrun 方法:作用为每个线程 都在 run 方法 中 通过 id 获取信 并且写入内容 mail


People


People 线程类 是寄信人类,作用是 创建信这个类的方法:run 方法:作用是创建信,并 在一段时间后 获取信的内容,如果 5s 后仍然没有获取到则放弃获取 说明信的内容写入超时


Test20


Test20 类是入口类创建了 3 个 People 线程与 3 个 Postman 线程,并且在 People 线程运行 1s 后 Postman 线程开始运行,模拟 1s 后送信到邮局

发布于: 刚刚阅读数: 3
用户头像

Geek_65222d

关注

还未添加个人签名 2022.09.09 加入

还未添加个人简介

评论

发布
暂无评论
【C语言难点突破】动态内存知识详解_十月月更_Geek_65222d_InfoQ写作社区