1、保护性暂停解耦
图中Futures就好比居民楼的信箱(每个信箱都有房间编号),左侧t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员。
如果需要在多个类之间使用GuardedObject对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦结果等待者和结果生产者,还能够同时支持多个任务的管理。
场景:邮递员送信,居民收信,信箱里面有很多格子,每个格子对应居民房间,编号。邮递员根据信件的房间编号,把信件投入相应的信箱小格子;居民根据自己的房间编号去对应的小格子中去信件,读取内容。
/**
* 信箱类
*/
public class MailBoxes {
// 容器,存放信件
private static Hashtable<String, GuardedDecouple> container = new Hashtable<>();
/**
* 根据居民房间编号初始化信箱
* @param identifiers 居民房间编号
*/
public static void init(String[] identifiers) {
for (String identifier: identifiers) {
container.put(identifier, new GuardedDecouple(identifier));
}
}
/**
* 根据房间编号获取信件
* @param identifier 房间编号
* @return 信件
*/
public synchronized static GuardedDecouple getLetter(String identifier) {
return container.get(identifier);
}
/**
* 设置信件-快递员把信件投入收件箱
* @param guardedDecouple
*/
public synchronized static void setLetter(GuardedDecouple guardedDecouple) {
container.put(guardedDecouple.getIdentifier(), guardedDecouple);
}
}
/**
* 居民类
*/
@Slf4j(topic = "d.Resident")
public class Resident implements Runnable{
private String identifier;
public Resident(String identifier) {
this.identifier = identifier;
}
@Override
public void run() {
// 收信
GuardedDecouple letter = MailBoxes.getLetter(identifier);
log.debug("等待收信");
Object content = letter.get(5000);
log.debug("居民房间编号:{},信已收到,内容:{}", letter.getIdentifier(), content);
}
}
/**
* 邮递员
*/
@Slf4j(topic = "d.Postman")
public class Postman implements Runnable{
private String identifier;
private String mail;
public Postman(String identifier, String mail) {
this.identifier = identifier;
this.mail = mail;
}
@Override
public void run() {
// 送信件
log.debug("信已送到,居民编号:{}", identifier);
GuardedDecouple letter = MailBoxes.getLetter(identifier);
letter.setResponse(mail);
MailBoxes.setLetter(letter);
}
}
/**
* 送信测试
*/
@Slf4j(topic = "d.GuardedTest03")
public class GuardedTest03 {
public static void main(String[] args) throws InterruptedException {
// 信件
HashMap<String, String> mails = new HashMap<>();
mails.put("101", "你好吗?老朋友");
mails.put("102", "要买房吗?");
mails.put("307", "您的快递到了,请签收");
// 居民房间编号
String[] identifiers = {"101", "102", "307"};
// 初始化信箱
MailBoxes.init(identifiers);
// 模拟3个居民
for (String identifier: identifiers) {
new Thread(new Resident(identifier), "居民" + identifier).start();
}
// 模拟3个快递员
TimeUnit.SECONDS.sleep(1);
for (String identifier: identifiers) {
new Thread(new Postman(identifier, mails.get(identifier)), "邮递员" + identifier).start();
}
}
}
- 这里只模拟了信箱里面小格子只接收1封信件,如果想要接收多个信件,自行改造为集合类型。
2、生产者消费者模式
邮递员和居民一一对应,成本太高。以送‘四通一达’送快递为例,通常一片区域,每家快递公司配置一名快递员,快递到达快递公司,指派该片快递员送至小区统一快递点。居民也不需要提前等待取件,什么时候有空了什么时候去取件。这里可以该为生产者消费者模式实现。
-
与前面的保护性暂停不同,不需要产生结果和消费结果的线程一一对应。
-
消费队列可以平衡生产和消费的线程资源
-
生产者仅负责产生结果,不关心数据什么时间、如何被处理;消费者负责消息的处理。
-
消息队列有容量限制,当消息队列满时,不能在往消息队列添加消息;当消息队列空时,不能取消息。
-
JDK中的各种阻塞队列,采用的就是这种模式
生产者消费者小案例:生产者若干,消费者若干。生产者生产消息放入消息队列,消费者从消息队列中取消息;等消息队列空时,消费者等待生产者放入消息;当消息队列满时,生产者等待消费者取消息;本例模拟3个生产者,1个消费者。
/**
* 消息类
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Message {
/** 唯一标志 */
private int id;
/** 内容 */
private Object value;
}
/**
* 消息队列
*/
@Slf4j(topic = "c.MessageQueue")
public class MessageQueue {
/** 消息容器 */
LinkedList<Message> queue = new LinkedList<>();
/** 队列容量 */
private int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
}
/**
* 获取消息
* @return 消息
*/
public Message getMessage() {
synchronized (queue) {
// 检查消息队列是否为空
while (queue.size() == 0) {
// 消息队列为空不能获取消息,生产者线程等待
try {
log.debug("消息队列为空,线程-{}等待",Thread.currentThread().getName());
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = queue.removeFirst();
log.debug("线程-{}消费了消息:{}", Thread.currentThread().getName(), message);
// 通知生产者可以添加消息
queue.notifyAll();
return message;
}
}
/**
* 添加消息
* @param message
*/
public void putMessage(Message message) {
synchronized (queue) {
// 检查消息队列是否已满
while (queue.size() == capacity) {
// 如果消息队列已满,生产者线程等待
try {
log.debug("消息队列已满,线程-{}等待",Thread.currentThread().getName());
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(message);
log.debug("线程-{}生产了消息:{}", Thread.currentThread().getName(), message);
// 通知消费者可以获取消息
queue.notifyAll();
}
}
}
@Slf4j(topic = "c.MessageQueueTest01")
public class MessageQueueTest01 {
public static void main(String[] args) throws InterruptedException {
// 创建消息队列,初始容量2
MessageQueue queue = new MessageQueue(2);
// 模拟3个生产者线程
for (int i = 0; i < 3; i++) {
int id = i;
new Thread(() -> {
queue.putMessage(new Message(id, "啦啦啦啦" + id));
}, "生产者" + i).start();
}
// 等待1s
TimeUnit.SECONDS.sleep(1);
// 模拟1个消费者线程
new Thread(() -> {
while (true) {
Message message = queue.getMessage();
}
}, "消费者").start();
}
}