3

java | 设计模式 同步模式-保护性暂停 解耦多模式

 1 year ago
source link: https://benpaodewoniu.github.io/2022/12/18/java112/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

java | 设计模式 同步模式-保护性暂停 解耦多模式

的再次升级分析。

之前虽然可以进行线程间的唤醒,但是,都是一个线程借助一个锁,唤醒另外一个线程。

但是,我们想多个线程借助一个模块,唤醒多个对应线程。

112_0.svg

设计一个用来解耦的中间类,不仅能够解耦「结果等待者」和「结果生产者」,同时还能够同时支持多个任务的管理。

考虑以下的场景

  • 有一个相亲晚会,相亲晚会的一个节目是,女生收到男生的情书
  • 每个女生只能收到一个情书
  • 男生只能有一个心仪的女生,并且写情书,为了公平,男生选择好一个女生后,其他男生就不能选择该女生了
  • 在规定时间内,男生把情书写好,放到信箱中,等待心仪女生获取

上面只是一个小场景,不必特别在意「其实是为了代码逻辑,硬想出的场景」,但是,有一个限制点需要特别提示一下

  • 女生和男生之间是点对点连接
  • 信箱只是暂时存放信件
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

@Slf4j(topic = "c.Run")
public class Run {

public static void main(String[] args) throws Exception {
for (int i = 0; i < 3; i++) {
new girl().start();
}
Thread.sleep(1000);

for (Integer id : Mailboxes.getIds()) {
new boy(id, "情书内容:做我女朋友吧" + id).start();
}
}

}

@Slf4j(topic = "c.Girl")
class girl 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.Boy")
class boy extends Thread {
private int id;
private String mail;

public boy(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() {
// 为什么不需要加 synchronized,是因为 Hashtable 本来就是线程安全的
return boxes.keySet();
}
}

class GuardedObject {
private int id;
private Object response;

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

public int getId() {
return id;
}

// 获取结果
public Object get(long timeout) {
// 开始时间
long begin = System.currentTimeMillis();
synchronized (this) {
// 经历时间
long passedTime = 0;
// 解决虚假唤醒
while (response == null) {
if (passedTime >= timeout) {
break;
}
try {
this.wait(timeout - passedTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
passedTime = System.currentTimeMillis() - begin;
}
return response;
}
}

// 产生结果
public void complete(Object response) {
synchronized (this) {
// 给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
14:30:16.034 [Thread-2] DEBUG c.Girl - 等待情书 女生 id 2
14:30:16.035 [Thread-1] DEBUG c.Girl - 等待情书 女生 id 3
14:30:16.034 [Thread-0] DEBUG c.Girl - 等待情书 女生 id 1
14:30:17.048 [Thread-4] DEBUG c.Boy - 写情书并发送 id:2 内容 情书内容:做我女朋友吧2
14:30:17.048 [Thread-3] DEBUG c.Boy - 写情书并发送 id:3 内容 情书内容:做我女朋友吧3
14:30:17.049 [Thread-5] DEBUG c.Boy - 写情书并发送 id:1 内容 情书内容:做我女朋友吧1
14:30:17.049 [Thread-1] DEBUG c.Girl - 收到情书 女生 id:3 内容:情书内容:做我女朋友吧3
14:30:17.049 [Thread-2] DEBUG c.Girl - 收到情书 女生 id:2 内容:情书内容:做我女朋友吧2
14:30:17.049 [Thread-0] DEBUG c.Girl - 收到情书 女生 id:1 内容:情书内容:做我女朋友吧1
112_0.svg
  • Mailboxes 是图中的 Futures
  • MailBoxes 暂时存放中间信息传递

这时候有的人就问,必须要 MailBoxes 吗?答案是当然可以,看下面的代码

package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.util.*;


@Slf4j(topic = "c.Run")
public class Run {

public static void main(String[] args) throws Exception {
// 这里其实拿到信件后,要删除,要是没删除,后续可能会撑爆内存
List<girl> girls = new ArrayList<>();
for (int i = 0; i < 3; i++) {
girl girl_ = new girl();
girls.add(girl_);
girl_.start();
}
Thread.sleep(1000);

girls.forEach(
(g) -> {
new boy(g, "做我女朋友").start();
}
);
}

}

@Slf4j(topic = "c.Girl")
class girl extends Thread {

private GuardedObject guardedObject = new GuardedObject();

public GuardedObject getGuardedObject() {
return guardedObject;
}

@Override
public void run() {
// 收信
log.debug("等待情书 女生");
Object mail = guardedObject.get(5000);
log.debug("收到情书 女生 内容:{}", mail);
}
}

@Slf4j(topic = "c.Boy")
class boy extends Thread {
private String mail;
private girl girl_;

public boy(girl girl_, String mail) {
this.girl_ = girl_;
this.mail = mail;
}

@Override
public void run() {
log.debug("写情书并发送 内容 {}", mail);
this.girl_.getGuardedObject().complete(mail);
}
}

class GuardedObject {
private Object response;

// 获取结果
public Object get(long timeout) {
// 开始时间
long begin = System.currentTimeMillis();
synchronized (this) {
// 经历时间
long passedTime = 0;
// 解决虚假唤醒
while (response == null) {
if (passedTime >= timeout) {
break;
}
try {
this.wait(timeout - passedTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
passedTime = System.currentTimeMillis() - begin;
}
return response;
}
}

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

功能当然可以实现,但是,耦合性非常高,不利于以后的功能替换。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK