同步模式之保护性暂停、异步模式之生产者/消费者

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

1.1 概述

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

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

在这里插入图片描述

1.2 实现

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

/**
 * 多线程同步模式 - 一个线程需要等待另一个线程的执行结果
 */
@Slf4j(topic = "GuardeObjectTest")
public class GuardeObjectTest {
    public static void main(String[] args) {

        // 线程1等待线程2的下载结果
        GuardeObject guardeObject = new GuardeObject();

        new Thread(() -> {
            log.debug("等待结果");
            Object obj = guardeObject.get();
            log.debug("结果:{}", obj.toString());
        }, "t1").start();

        new Thread(() -> {

            log.debug("执行下载");
            String result = "下载成功";
            guardeObject.complete(result);
            // 可以继续执行别的任务

        }, "t2").start();
    }
}


class GuardeObject {

    // 结果
    private Object response;

    // 获取结果
    public Object get() {
        synchronized (this) {
            // 防止虚假唤醒
            // 没有结果
            while (response == null) {
                try {
                    // 释放锁
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

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

运行结果

2022-03-09 21:19:32 [t1] - 等待结果
2022-03-09 21:19:32 [t2] - 执行下载
2022-03-09 21:19:32 [t1] - 结果:下载成功

Process finished with exit code 0

增强超时效果

/**
 * 多线程同步模式 - 一个线程需要等待另一个线程的执行结果
 */
@Slf4j
public class GuardeObjectTest {

    public static void main(String[] args) {
        // 线程1等待线程2的下载结果
        GuardeObject guardeObject = new GuardeObject();

        new Thread(() -> {
            log.debug("begin");
            Object obj = guardeObject.get(2000);
            log.debug("结果是:{}", obj);
        }, "t1").start();


        new Thread(() -> {
            log.debug("begin");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardeObject.complete("hello word");
        }, "t2").start();
    }
}

class GuardeObject {
    // 结果
    private Object response;

    // 获取结果
    // timeout表示等待多久. 这里假如是2s
    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 (waitTime <= 0) {
                    break;
                }
                try {
                    // this.wait(timeout)的问题: 虚假唤醒在15:00:01的时候,此时response还null, 此时经历时间就为1s,
                    // 进入while循环的时候response还是空,此时已经经历了1s,
                    // 所以只要再等1s就可以了. 所以等待的时间应该是 超时时间(timeout) - 经历的时间(passedTime)
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 经历时间
                passedTime = System.currentTimeMillis() - begin; // 15:00:02
            }
            return response;
        }
    }

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

运行结果

// 超时
2022-03-09 21:57:07 [t1] - begin
2022-03-09 21:57:07 [t2] - begin
2022-03-09 21:57:09 [t1] - 结果是:null

Process finished with exit code 0

// 没有超时
2022-03-09 21:58:29 [t2] - begin
2022-03-09 21:58:29 [t1] - begin
2022-03-09 21:58:30 [t1] - 结果是:hello word

Process finished with exit code 0

1.3 join原理

join源码
synchronized在join方法上,在主线程therd1.join锁的对象是therd1,线程对象作为锁运行完后会自动唤醒在它上面wait的主线程

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
        	// 该线程存活就一直等待, 存活:true
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

1.4 扩展-多任务版

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

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

在这里插入图片描述

/**
 * Description: 同步模式保护性暂停模式 (多任务版)
 */
@Slf4j
class GuardedObjectTest {
    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 3; i++) {
            new People().start();
        }

        Thread.sleep(1000);
        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "内容" + id).start();
        }
    }
}

@Slf4j
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
// 邮寄员类
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) {
        // 根据id获取到box并删除对应的key和value,避免堆内存爆了
        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();
    }
}

// 用来传递信息的作用, 当多个类使用GuardedObject,就很不方便,此时需要一个设计一个解耦的中间类
class GuardedObject {
    // 标记GuardedObject
    private int id;
    // 结果
    private Object response;

    public int getId() {
        return id;
    }

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

    // 获取结果
    // timeout表示等待多久. 这里假如是2s
    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 (waitTime <= 0) {
                    break;
                }
                try {
                    // this.wait(timeout)的问题: 虚假唤醒在15:00:01的时候,此时response还null, 此时经历时间就为1s,
                    // 进入while循环的时候response还是空, 此时已经经历了
                    // 1s,所以只要再等1s就可以了. 所以等待的时间应该是 超时时间(timeout) - 经历的时间(passedTime)
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 经历时间
                passedTime = System.currentTimeMillis() - begin; // 15:00:02
            }
            return response;
        }
    }

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

运行结果

2022-03-09 22:35:52 [Thread-2] - 开始收信 id:1
2022-03-09 22:35:52 [Thread-0] - 开始收信 id:3
2022-03-09 22:35:52 [Thread-1] - 开始收信 id:2
2022-03-09 22:35:53 [Thread-3] - 送信 id:3, 内容:内容3
2022-03-09 22:35:53 [Thread-4] - 送信 id:2, 内容:内容2
2022-03-09 22:35:53 [Thread-5] - 送信 id:1, 内容:内容1
2022-03-09 22:35:53 [Thread-1] - 收到信 id:2, 内容:内容2
2022-03-09 22:35:53 [Thread-2] - 收到信 id:1, 内容:内容1
2022-03-09 22:35:53 [Thread-0] - 收到信 id:3, 内容:内容3

Process finished with exit code 0

2.异步模式之生产者/消费者

2.1 概述

在这里插入图片描述

2.2 实现

/**
 * Description: 异步模式之生产者/消费者
 */
@Slf4j(topic = "z.ProductConsumerTest")
public class ProductConsumerTest {
    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);

        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id, "值" + id));
            }, "生产者" + i).start();
        }

        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = queue.take();
            }
        }, "消费者").start();
    }

}

// 消息队列类,在线程之间通信
@Slf4j(topic = "z.MessageQueue")
class MessageQueue {
    // 消息的队列集合
    private LinkedList<Message> list = new LinkedList<>();
    // 队列容量
    private int capcity;

    public MessageQueue(int capcity) {
        this.capcity = capcity;
    }

    // 获取消息
    public Message take() {
        // 检查队列是否为空
        synchronized (list) {
            while (list.isEmpty()) {
                try {
                    log.debug("队列为空, 消费者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 从队列头部获取消息并返回
            Message message = list.removeFirst();
            log.debug("已消费消息 {}", message);
            list.notifyAll();
            return message;
        }
    }

    // 存入消息
    public void put(Message message) {
        synchronized (list) {
            // 检查对象是否已满
            while (list.size() == capcity) {
                try {
                    log.debug("队列已满, 生产者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 将消息加入队列尾部
            list.addLast(message);
            log.debug("已生产消息 {}", message);
            list.notifyAll();
        }
    }
}

final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}

运行结果

2022-03-09 23:10:40 [生产者0] - 已生产消息 Message{id=0, value=0}
2022-03-09 23:10:40 [生产者1] - 已生产消息 Message{id=1, value=1}
2022-03-09 23:10:40 [生产者2] - 队列已满, 生产者线程等待
2022-03-09 23:10:41 [消费者] - 已消费消息 Message{id=0, value=0}
2022-03-09 23:10:41 [生产者2] - 已生产消息 Message{id=2, value=2}
2022-03-09 23:10:42 [消费者] - 已消费消息 Message{id=1, value=1}
2022-03-09 23:10:43 [消费者] - 已消费消息 Message{id=2, value=2}
2022-03-09 23:10:44 [消费者] - 队列为空, 消费者线程等待

Process finished with exit code 130 (interrupted by signal 2: SIGINT)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值