同步模式-保护性暂停
1.概念
即 Guarded Suspension,用在一个线程等待另一个线程的执行结果。
要点:
- 有一个结果需要从一个线程传递到另一个线程,让他们
关联同一个 GuardedObject
- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
- JDK 中,
join 的实现
、Future 的实现
,采用的就是此模式 - 因为要等待另一方的结果,因此归类到同步模式
保护性暂停模式的特点:
- 与join()相比,不用等到另一个线程结束再执行别的代码。
- 等待结果的变量能够设置成局部的,不用设置成全局的。
2.GuardedObject代码和测试代码
模拟网上下载
package com.concurrent.p3;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* 同步模式-保护性暂停
*/
@Slf4j(topic = "c.TestModel_GuardedObject")
public class TestModel_GuardedObject {
@Test
public void testGuardedObject() {
//定义共享对象
GuardedObject guardedObject = new GuardedObject();
//线程1执行下载
Thread t1 = new Thread(() -> {
log.debug("正在下载,请稍等...");
List<String> list = (ArrayList<String>) WebDownload.download();
guardedObject.complete(list);
log.debug("已经完成下载");
}, "t1");
t1.start();
//线程2获取下载结果
Thread t2 = new Thread(() -> {
log.debug("获取下载内容...");
List<String> list = (ArrayList<String>) guardedObject.get();
list.forEach((s) -> {
log.debug(s);
});
}, "t2");
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 线程同步对象:GuardedObject
*/
class GuardedObject {
//同步对象
private Object resp;
//获取结果
public Object get() {
synchronized (this) {
//循环判断,如果当前结果为空则等待;不为空则返回结果
while (this.resp== null) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return resp;
}
}
//产生结果
public void complete(Object resp) {
synchronized (this) {
this.resp= resp;
//唤醒所有等待线程
this.notifyAll();
}
}
}
/**
* 模拟网页下载器:WebDownload
*/
@Slf4j(topic = "c.WebDownload")
class WebDownload {
public static Object download() {
List<String> list = new ArrayList<>();
//模拟5秒内完成下载
for (int i = 0; i < 5; i++) {
log.debug("还需要{}秒完成下载", (5 - i));
try {
Thread.sleep(1000);
list.add("Java" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return list;
}
}
3.扩展1-添加获取超时参数
//获取结果扩展-添加获取超时参数
//timeout 最大等待时间
public Object get(long timeout) {
synchronized (this) {
long passedTime = 0;
long beginTime = System.currentTimeMillis();
//循环判断,如果当前结果为空则等待;不为空则返回结果
while (this.resp == null) {
long waitTime = timeout - passedTime;
//如果经历时间超过超时时间,返回
if (waitTime <= 0) {
break;
}
try {
this.wait(waitTime); //避免虚假唤醒15:00:01
} catch (InterruptedException e) {
e.printStackTrace();
}
passedTime = System.currentTimeMillis() - beginTime;
}
return resp;
}
}
4.扩展2-多任务版GuardedObject
图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右侧的 t1,t3,t5 就好比邮递员。
如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。
新增 id 用来标识 Guarded Object。
同步类MailGuardedObject:
package com.concurrent.p3.multiGuardedObject;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.MailGuardedObject")
public class MailGuardedObject {
//标识 GuardedObject resp
private Integer id;
private Object resp;
public MailGuardedObject(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
//产生结果
public void complete(Object obj) {
synchronized (this) {
this.resp = obj;
this.notifyAll();
}
}
//获取结果
public Object get() {
synchronized (this) {
while (resp == null) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return resp;
}
}
//超时获取结果
public Object get(long timeout) {
synchronized (this) {
long begin = System.currentTimeMillis();
long passTime = 0; //经历时间
while (resp == null) {
long waitTime = timeout - passTime;
if (waitTime <= 0) {
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passTime = System.currentTimeMillis() - begin;
}
return resp;
}
}
}
中间解耦类 MailBox
package com.concurrent.p3.multiGuardedObject;
import lombok.extern.slf4j.Slf4j;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
/**
* 中间解耦类
* 解耦结果的产生者和结果获取者
* RPC框架常用
*/
@Slf4j(topic = "c.MailBox")
public class MailBox {
//HashTable是线程安全的
private static Map<Integer, MailGuardedObject> mailBoxs = new Hashtable<>();
//唯一标识
private static int id = 1;
//id递增
private synchronized static int generateId() {
return id++;
}
//产生MailGuardedObject
public static MailGuardedObject createMailGuardedObject() {
//创建MailGuardedObject对象,id自增
MailGuardedObject mailGuardedObject = new MailGuardedObject(generateId());
mailBoxs.put(mailGuardedObject.getId(), mailGuardedObject);
return mailGuardedObject;
}
//根据id获取MailGuardedObject
public static MailGuardedObject getMailGuardedObject(int id) {
return mailBoxs.remove(id);
}
//返回编id集合
public static Set<Integer> getIds() {
return mailBoxs.keySet();
}
}
收信人类 User
package com.concurrent.p3.multiGuardedObject;
import lombok.extern.slf4j.Slf4j;
/**
* 用户线程,获取邮件
*/
@Slf4j(topic = "c.User")
public class User extends Thread {
@Override
public void run() {
//收信
MailGuardedObject mailGuardedObject = MailBox.createMailGuardedObject();
log.debug("开始收信 id:{}", mailGuardedObject.getId());
Object mail = mailGuardedObject.get(5000);
log.debug("收信 id:{},内容:{}", mailGuardedObject.getId(), mail);
}
}
邮递员类 Postman
package com.concurrent.p3.multiGuardedObject;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Postman")
public 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() {
MailGuardedObject mailGuardedObject = MailBox.getMailGuardedObject(id);
log.debug("送信id:{},内容:{}", mailGuardedObject.getId(), mail);
mailGuardedObject.complete(mail);
}
}
测试代码
import org.junit.Test;
@Slf4j(topic = "c.TestModel_MultiGuardedObject")
public class TestModel_MultiGuardedObject {
@Test
public void test() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new User().start();
}
Thread.sleep(1000);
for (Integer id : MailBox.getIds()) {
new Postman(id, "[content:" + id + "]").start();
}
Thread.sleep(5000);
}
}
5.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) { //等待时间小于0,不合法
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) { //等待时间等于0,会一直等待下去。wait()
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
// 防止错误唤醒
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}