经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
Java并发(二十三)----同步模式之保护性暂停
来源:cnblogs  作者:|旧市拾荒|  时间:2024/2/5 10:00:03  对本文有异议

1、定义

即 Guarded Suspension,用在一个线程等待另一个线程的执行结果

要点

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

  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列

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

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

2、实现

  1. class GuardedObject {
  2. ?
  3.    // 结果
  4.    private Object response;
  5.    private final Object lock = new Object();
  6. ?
  7.    // 获取结果
  8.    public Object get() {
  9.        synchronized (lock) {
  10.            // 条件不满足则等待
  11.            while (response == null) {
  12.                try {
  13.                    lock.wait();
  14.               } catch (InterruptedException e) {
  15.                    e.printStackTrace();
  16.               }
  17.           }
  18.            return response;
  19.       }
  20.   }
  21. ?
  22.    // 产生结果
  23.    public void complete(Object response) {
  24.        synchronized (lock) {
  25.            // 条件满足,通知等待线程
  26.            this.response = response;
  27.            lock.notifyAll();
  28.       }
  29.   }
  30. }

3、应用

一个线程等待另一个线程的执行结果

  1. public static void main(String[] args) {
  2.    GuardedObject guardedObject = new GuardedObject();
  3.    new Thread(() -> {
  4.        try {
  5.            // 子线程执行下载
  6.            List<String> response = download(); // 模拟下载操作
  7.            log.debug("download complete...");
  8.            guardedObject.complete(response);
  9.       } catch (IOException e) {
  10.            e.printStackTrace();
  11.       }
  12.   }).start();
  13. ?
  14.    log.debug("waiting...");
  15.    // 主线程阻塞等待
  16.    Object response = guardedObject.get();
  17.    log.debug("get response: [{}] lines", ((List<String>) response).size());
  18. ?
  19. }

执行结果

  1. 08:42:18.568 [main] c.TestGuardedObject - waiting...
  2. 08:42:23.312 [Thread-0] c.TestGuardedObject - download complete...
  3. 08:42:23.312 [main] c.TestGuardedObject - get response: [3] lines

4、带超时版 GuardedObject

如果要控制超时时间呢

  1. class GuardedObjectV2 {
  2. ?
  3.    private Object response;
  4.    private final Object lock = new Object();
  5. ?
  6.    public Object get(long millis) {
  7.        synchronized (lock) {
  8.            // 1) 记录最初时间
  9.            long begin = System.currentTimeMillis();
  10.            // 2) 已经经历的时间
  11.            long timePassed = 0;
  12.            while (response == null) {
  13.                // 4) 假设 millis 是 1000,结果在 400 时唤醒了,那么还有 600 要等
  14.                long waitTime = millis - timePassed;
  15.                log.debug("waitTime: {}", waitTime);
  16.                if (waitTime <= 0) {
  17.                    log.debug("break...");
  18.                    break;
  19.               }
  20.                try {
  21.                    lock.wait(waitTime);  // 注意这里并不是 mills,防止虚假唤醒
  22.               } catch (InterruptedException e) {
  23.                    e.printStackTrace();
  24.               }
  25.                // 3) 如果提前被唤醒,这时已经经历的时间假设为 400
  26.                timePassed = System.currentTimeMillis() - begin;
  27.                log.debug("timePassed: {}, object is null {}",
  28.                          timePassed, response == null);
  29.           }
  30.            return response;
  31.       }
  32.   }
  33. ?
  34.    public void complete(Object response) {
  35.        synchronized (lock) {
  36.            // 条件满足,通知等待线程
  37.            this.response = response;
  38.            log.debug("notify...");
  39.            lock.notifyAll();
  40.       }
  41.   }
  42. }

测试,没有超时

  1. public static void main(String[] args) {
  2.    GuardedObjectV2 v2 = new GuardedObjectV2();
  3.    new Thread(() -> {
  4.        sleep(1); // 睡眠1秒
  5.        v2.complete(null);
  6.        sleep(1);
  7.        v2.complete(Arrays.asList("a", "b", "c"));
  8.   }).start();
  9. ?
  10.    Object response = v2.get(2500);
  11.    if (response != null) {
  12.        log.debug("get response: [{}] lines", ((List<String>) response).size());
  13.   } else {
  14.        log.debug("can't get response");
  15.   }
  16. }

输出

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

测试,超时

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

输出

  1. 08:47:54.963 [main] c.GuardedObjectV2 - waitTime: 1500
  2. 08:47:55.963 [Thread-0] c.GuardedObjectV2 - notify...
  3. 08:47:55.963 [main] c.GuardedObjectV2 - timePassed: 1002, object is null true
  4. 08:47:55.963 [main] c.GuardedObjectV2 - waitTime: 498
  5. 08:47:56.461 [main] c.GuardedObjectV2 - timePassed: 1500, object is null true
  6. 08:47:56.461 [main] c.GuardedObjectV2 - waitTime: 0
  7. 08:47:56.461 [main] c.GuardedObjectV2 - break...
  8. 08:47:56.461 [main] c.TestGuardedObjectV2 - can't get response
  9. 08:47:56.963 [Thread-0] c.GuardedObjectV2 - notify...

5、多任务版 GuardedObject

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

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

新增 id 用来标识 Guarded Object

  1. class GuardedObject {
  2. ?
  3.    // 标识 Guarded Object
  4.    private int id;
  5. ?
  6.    public GuardedObject(int id) {
  7.        this.id = id;
  8.   }
  9. ?
  10.    public int getId() {
  11.        return id;
  12.   }
  13. ?
  14.    // 结果
  15.    private Object response;
  16. ?
  17.    // 获取结果
  18.    // timeout 表示要等待多久 2000
  19.    public Object get(long timeout) {
  20.        synchronized (this) {
  21.            // 开始时间 15:00:00
  22.            long begin = System.currentTimeMillis();
  23.            // 经历的时间
  24.            long passedTime = 0;
  25.            while (response == null) {
  26.                // 这一轮循环应该等待的时间
  27.                long waitTime = timeout - passedTime;
  28.                // 经历的时间超过了最大等待时间时,退出循环
  29.                if (timeout - passedTime <= 0) {
  30.                    break;
  31.               }
  32.                try {
  33.                    this.wait(waitTime); // 虚假唤醒 15:00:01
  34.               } catch (InterruptedException e) {
  35.                    e.printStackTrace();
  36.               }
  37.                // 求得经历时间
  38.                passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
  39.           }
  40.            return response;
  41.       }
  42.   }
  43. ?
  44.    // 产生结果
  45.    public void complete(Object response) {
  46.        synchronized (this) {
  47.            // 给结果成员变量赋值
  48.            this.response = response;
  49.            this.notifyAll();
  50.       }
  51.   }
  52. }

中间解耦类

  1. class Mailboxes {
  2.    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();
  3. ?
  4.    private static int id = 1;
  5.    // 产生唯一 id
  6.    private static synchronized int generateId() {
  7.        return id++;
  8.   }
  9. ?
  10.    public static GuardedObject getGuardedObject(int id) {
  11.        return boxes.remove(id);  // 注意这里的remove,防止堆溢出
  12.   }
  13. ?
  14.    public static GuardedObject createGuardedObject() {
  15.        GuardedObject go = new GuardedObject(generateId());
  16.        boxes.put(go.getId(), go);
  17.        return go;
  18.   }
  19. ?
  20.    public static Set<Integer> getIds() {
  21.        return boxes.keySet();
  22.   }
  23. }

业务相关类

  1. class People extends Thread{
  2.    @Override
  3.    public void run() {
  4.        // 收信
  5.        GuardedObject guardedObject = Mailboxes.createGuardedObject();
  6.        log.debug("开始收信 id:{}", guardedObject.getId());
  7.        Object mail = guardedObject.get(5000);
  8.        log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
  9.   }
  10. }
  1. class Postman extends Thread {
  2.    private int id;
  3.    private String mail;
  4. ?
  5.    public Postman(int id, String mail) {
  6.        this.id = id;
  7.        this.mail = mail;
  8.   }
  9. ?
  10.    @Override
  11.    public void run() {
  12.        GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
  13.        log.debug("送信 id:{}, 内容:{}", id, mail);
  14.        guardedObject.complete(mail);
  15.   }
  16. }

测试

  1. public static void main(String[] args) throws InterruptedException {
  2.    for (int i = 0; i < 3; i++) {
  3.        new People().start();
  4.   }
  5.    Sleeper.sleep(1);// 睡眠1秒
  6.    for (Integer id : Mailboxes.getIds()) {
  7.        new Postman(id, "内容" + id).start();
  8.   }
  9. }

某次运行结果

  1. 10:35:05.689 c.People [Thread-1] - 开始收信 id:3
  2. 10:35:05.689 c.People [Thread-2] - 开始收信 id:1
  3. 10:35:05.689 c.People [Thread-0] - 开始收信 id:2
  4. 10:35:06.688 c.Postman [Thread-4] - 送信 id:2, 内容:内容2
  5. 10:35:06.688 c.Postman [Thread-5] - 送信 id:1, 内容:内容1
  6. 10:35:06.688 c.People [Thread-0] - 收到信 id:2, 内容:内容2
  7. 10:35:06.688 c.People [Thread-2] - 收到信 id:1, 内容:内容1
  8. 10:35:06.688 c.Postman [Thread-3] - 送信 id:3, 内容:内容3
  9. 10:35:06.689 c.People [Thread-1] - 收到信 id:3, 内容:内容3

 

原文链接:https://www.cnblogs.com/xiaoyh/p/17161704.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号