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)