提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
上一章https://blog.csdn.net/m0_45364328/article/details/124936454
我们介绍了wait/notify的原理,为加深理解,我们本章介绍一下,由wait/notify延申出的设计模式
保护性暂停的定义
用一个线程等待另一个线程的执行结果
要点:
1、有一个结果要从一个线程传递到另一个线程,让它们关联同一个GuadedObject
2、如果有结果不断地从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
3、JDK中join的实现、Future的实现,采用的就是此模式
因为要对等待另一方的结果,因此归类到同步模式
简单实现
@Slf4j(topic = "c.Test20")
public class Test20 {
//线程1等待线程2的执行结果
public static void main(String[] args) {
GuadenObject guadenObject = new GuadenObject();
new Thread(new Runnable() {
@Override
public void run() {
Object o = guadenObject.get();
System.out.println(o);
}
},"t1").start();
new Thread(new Runnable() {
@Override
public void run() {
//执行任务
try {
Thread.sleep(3000);
String result = "结果";
guadenObject.complete(result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t2").start();
}
}
class GuadenObject{
//结果
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();
}
}
}
结果
增加超时等待
//超时
public Object get(long timeout){
synchronized (this){
//在进入循环等待前首先记录一下开始时间
long begin = System.currentTimeMillis();
long passedTime=0;
while (response==null){
if (passedTime>=timeout){//如果已经等待的时间超过了timeout就退出循环
break;
}
try {
this.wait(timeout-passedTime); //timeout-passedTime,还需要再等多少时间,解决虚假唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
//求得已经等待了多少时
passedTime= System.currentTimeMillis()-begin;
}
return response;
}
}
关于this.wait(timeout-passedTime)做一个说明:
//线程1等待线程2的执行结果
public static void main(String[] args) {
GuadenObject guadenObject = new GuadenObject();
new Thread(new Runnable() {
@Override
public void run() {
Object o = guadenObject.get();
System.out.println(o);
}
},"t1").start();
new Thread(new Runnable() {
@Override
public void run() {
//执行任务
try {
Thread.sleep(3000);
guadenObject.complete(null);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t2").start();
}
}
线程t2,执行complete给结果response赋值的时候,如果赋值的是null,也就说没有结果,唤醒t1,这个虚假唤醒
如果是this.wait(timeout)
while (response==null){
if (passedTime>=timeout){//如果已经等待的时间超过了timeout就退出循环
break;
}
try {
this.wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
//求得已经等待了多少时
passedTime= System.currentTimeMillis()-begin;
}
return response;
}
那么线程t1,就还要再等待timeout时间,那已经等待的passedTime就白等了,因此timeout-passedTime称为还需要等待的时间,就是解决上述虚假唤醒的状况。
join的原理
join的原理就是保护性暂停模式
下面看下join()的源码
public final void join() throws InterruptedException {
join(0);
}
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) {//只要线程还活着就一直等待
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {//只要线程活着(没有执行完),就循环等待
long delay = millis - now;//还需要等待的时间
if (delay <= 0) {不需要等待了,就跳出循环
break;
}
wait(delay);
now = System.currentTimeMillis() - base;//已经等待了的时间
}
}
}
扩展——保护性暂停模式的应用
@Slf4j(topic = "c.Test20")
public class Test20 {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 3; i++) {
new People().start();
}
Thread.sleep(1000);
for (Integer id : Mailboxes.getids()) {
new PostMan(id,"内容"+id).start();
}
}
}
/**
* 居民类
*/
@Slf4j(topic = "c.People")
class People extends Thread {
@Override
public void run() {
//收信
GuadenObject guadenObject = Mailboxes.createGuadenObject();//一个用户对应一个邮箱中的格子,一家一户
log.debug("开始收信 id:{}", guadenObject.getId());
Object mail = guadenObject.get();
log.debug("收到信 id:{},内容:{}", guadenObject.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() {
GuadenObject guadenObject = Mailboxes.getGuadenObject(id);
log.debug("送信 id:{},内容:{}", id, mail);
guadenObject.complete(mail);
}
}
/**
* 这个类相当于邮箱类
*/
@Slf4j(topic = "c.Mailboxes")
class Mailboxes {
private static Map<Integer, GuadenObject> boxes = new HashMap<>();
private static int id = 1;
public static GuadenObject getGuadenObject(int id) {
return boxes.remove(id);
}
//产生唯一id
private static synchronized int generateId() {
return id++;
}
public static GuadenObject createGuadenObject() {
GuadenObject guadenObject = new GuadenObject(generateId());
boxes.put(guadenObject.getId(), guadenObject);
return guadenObject;
}
public static Set<Integer> getids() {
return boxes.keySet();
}
}
/**
* 这个类就相当于邮箱中的格子类
*/
@Slf4j(topic = "c.GuadenObject")
class GuadenObject {
//标识 Guaden Object
private int id;
public GuadenObject(int id) {
this.id = id;
}
public int getId() {
return id;
}
//结果
private Object response;
//超时
public Object get(long timeout) {
synchronized (this) {
//在进入循环等待前首先记录一下开始时间
long begin = System.currentTimeMillis();
long passedTime = 0;
while (response == null) {
if (passedTime >= timeout) {//如果已经等待的时间超过了timeout就退出循环
break;
}
try {
this.wait(timeout - passedTime); //timeout-passedTime,还需要再等多少时间
} catch (InterruptedException e) {
e.printStackTrace();
}
//求得已经等待了多少时
passedTime = System.currentTimeMillis() - begin;
}
return 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();
}
}
}
01:35:20.400 [Thread-1] DEBUG c.People - 开始收信 id:1
01:35:20.401 [Thread-0] DEBUG c.People - 开始收信 id:2
01:35:20.402 [Thread-2] DEBUG c.People - 开始收信 id:3
01:35:21.403 [Thread-3] DEBUG c.PostMan - 送信 id:1,内容:内容1
01:35:21.403 [Thread-1] DEBUG c.People - 收到信 id:1,内容:内容1
01:35:21.403 [Thread-4] DEBUG c.PostMan - 送信 id:2,内容:内容2
01:35:21.403 [Thread-0] DEBUG c.People - 收到信 id:2,内容:内容2
01:35:21.404 [Thread-5] DEBUG c.PostMan - 送信 id:3,内容:内容3
01:35:21.404 [Thread-2] DEBUG c.People - 收到信 id:3,内容:内容3
由此可以看出保护性暂停模式是一种一对一的模式,是很多RPC框架的底层,使用Mailboxes 解耦结果的产生者和消费者
异步模式之生产者消费者模式
@Slf4j(topic = "c.Message")
public class Test21 {
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));
},"生产者"+id).start();
}
new Thread(()->{
while (true){
try {
Thread.sleep(1000);
Message message = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
@Slf4j(topic = "c.Message")
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()) {
log.debug("队列为空,消费者线程等待");
try {
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) {
log.debug("队列已满,生成者线程等待");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.addLast(message);
log.debug("已生成消息{}"+message);
list.notifyAll();
}
}
}
/**
* Message类用final修饰和不添加属性的set方法,是防止其他类继承Message,或者修改属性
* 这样Message就是线程安全的了
*/
@Slf4j(topic = "c.Message")
final class Message {
private int id;
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Message(int id, String value) {
this.id = id;
this.value = value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value='" + value + '\'' +
'}';
}
}
10:54:14.468 [生产者1] DEBUG c.Message - 已生成消息{}Message{id=1, value='值1'}
10:54:14.469 [生产者0] DEBUG c.Message - 队列已满,生成者线程等待
10:54:15.472 [Thread-0] DEBUG c.Message - 已消费消息{}Message{id=2, value='值2'}
10:54:15.472 [生产者0] DEBUG c.Message - 已生成消息{}Message{id=0, value='值0'}
10:54:16.485 [Thread-0] DEBUG c.Message - 已消费消息{}Message{id=1, value='值1'}
10:54:17.495 [Thread-0] DEBUG c.Message - 已消费消息{}Message{id=0, value='值0'}
10:54:18.508 [Thread-0] DEBUG c.Message - 队列为空,消费者线程等待