保护性暂停是什么?
定义
即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
要点
有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
JDK 中,join 的实现、Future 的实现,采用的就是此模式
因为要等待另一方的结果,因此归类到同步模式
普通方式
package com.bo.threadstudy.four;
import lombok.extern.slf4j.Slf4j;
/**
* 保护性暂停模式
* 即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
* 要点
* 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
* 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
* JDK 中,join 的实现、Future 的实现,采用的就是此模式
* 因为要等待另一方的结果,因此归类到同步模式
*/
@Slf4j
public class ProtectiveSuspensionTest {
//其实就是线程间传递结果信息的问题,我先自己写一个
public static void main(String[] args) throws InterruptedException {
GuardedSuspension guardedSuspension = new GuardedSuspension();
new Thread(() -> {
String s = (String)guardedSuspension.get();
log.debug(s);
}).start();
log.debug("开始");
Thread.sleep(2000);
guardedSuspension.set("你的名字");
}
}
//这里的话我按泛型来写吧,好久没用了
class GuardedSuspension<T>{
private T obj;
private Object lock = new Object();
public T get(){
synchronized (lock){
while(obj == null){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒后再执行操作
}
return obj;
}
//底层用到notify,wait,肯定得加锁
public void set(T obj){
synchronized (lock){
this.obj = obj;
lock.notifyAll();
}
}
}
这里要用wait的原因,一方面多线程操作数据误差,另一个,如果不用wait,死循环会占用很多CPU资源。
wait超时版本
在wait上加超时,这个东西就在等待时间结束后自动释放,防止锁死。
在这里,我直接在wait后面加个时间是不可以的,要这么想,我时间完了,然后此时obj对象没有值,继续while循环,然后继续wait等待时间,又是死循环。
所以,我们需要添加一个条件。可以让这个等待时间到了后跳出循环。以第一次进入同步方法为初始时间,假设我中间被其它线程唤醒了,记录一下唤醒时间。然后,我是否已经获得我所期望的值,如果获得,那么就可以 继续往后面走了。如果没有获得,判断我现在的等待时间,和期望等待时间比较大小,如果时间已经到了,那么跳出循环,没有到,则继续等待剩余的时间。
package com.bo.threadstudy.four;
import lombok.extern.slf4j.Slf4j;
/**
* 保护性暂停模式
* 即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
* 要点
* 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
* 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
* JDK 中,join 的实现、Future 的实现,采用的就是此模式
* 因为要等待另一方的结果,因此归类到同步模式
*/
@Slf4j
public class ProtectiveSuspensionTest {
//其实就是线程间传递结果信息的问题,我先自己写一个
public static void main(String[] args) throws InterruptedException {
GuardedSuspension guardedSuspension = new GuardedSuspension();
new Thread(() -> {
String s = (String)guardedSuspension.get(1000);
log.debug(s);
}).start();
log.debug("开始");
Thread.sleep(3000);
guardedSuspension.set("你的名字");
}
}
//这里的话我按泛型来写吧,好久没用了
class GuardedSuspension<T>{
private T obj;
private Object lock = new Object();
public T get(long time){
synchronized (lock){
//最初的时间
long begin = System.currentTimeMillis();
//已经经历的时间
long timePassed = 0;
while(obj == null){
try {
if(timePassed < time){
lock.wait(time - timePassed);
}else{
break;
}
timePassed = System.currentTimeMillis() - begin;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒后再执行操作
}
return obj;
}
//底层用到notify,wait,肯定得加锁
public void set(T obj){
synchronized (lock){
this.obj = obj;
lock.notifyAll();
}
}
}
多任务版GuardedObject
图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右
侧的 t1,t3,t5 就好比邮递员
如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,
这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理
就相当于现在有一个大快递柜,然后送件人和取件人是一对一的关系。完成操作。
老师写的代码对我而言,不好理解,我自己按自己的思路写了一个。没有线程安全问题。
package com.bo.threadstudy.four;
import lombok.extern.slf4j.Slf4j;
import java.util.Hashtable;
import java.util.LinkedList;
/**
* 多个元素间的保护性暂停模式
*/
@Slf4j
public class ManyProtectiveSuspensionTest {
public static void main(String[] args) {
MiddleObjList middleObjList = new MiddleObjList();
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
MiddleObj<String> obj = new MiddleObj<String>(finalI);
obj.set("送你个礼物"+finalI);
log.debug("送你个礼物"+finalI);
middleObjList.produce(obj);
},"邮递员"+i).start();
}
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
MiddleObj middleObj = middleObjList.custome(finalI);
String s = (String)middleObj.get(1000);
log.debug(finalI+"礼物已收到:"+s);
},"收信人"+i).start();
}
}
}
/**
* 中间对象
*/
class MiddleObj<T>{
private T response;
private Object lock = new Object();
//需要id来做一个一对一的关系
private int id;
public MiddleObj(int id) {
this.id = id;
}
public int getId() {
return id;
}
public T get(long time){
synchronized (lock){
long begin = System.currentTimeMillis();
//记录一个中间时间
long passedTime = 0;
while(response == null){
//需要阻塞至时间到头
long middleTime = time - passedTime;
if(middleTime <= 0){
break;
}
//阻塞线程时间
try {
lock.wait(middleTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passedTime = System.currentTimeMillis() - begin;
}
}
return response;
}
public void set(T response){
synchronized (lock){
this.response = response;
lock.notifyAll();
}
}
}
//存放中间对象
class MiddleObjList{
private Hashtable<Integer,MiddleObj> table = new Hashtable<Integer,MiddleObj>();
//这个消费可以通过ID来进行消费,这里应该也是分为生产消费两种方法
//生产
public void produce(MiddleObj obj){
//得考虑并发插入的情况,不过Hashtable是线程安全的,所以暂时先不用考虑
table.put(obj.getId(),obj);
synchronized (this){
this.notifyAll();
}
}
//消费
public MiddleObj custome(int id){
//消费完成后,需要移除
while(!table.containsKey(id)){
try {
synchronized (this){
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
MiddleObj middleObj = table.remove(id);
return middleObj;
}
}