同步模式之保护性暂停

黑马视频讲解:https://www.bilibili.com/video/BV16J411h7Rd?p=98&spm_id_from=333.1007.top_right_bar_window_history.content.click

定义

英文为Guarded Suspension,用在一个线程等待另一个线程的执行结果。

要点:

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject
  • 如果有结果不断的从一个线程传递到另一个线程那么可以使用消息队列(见生产者/消费者)
  • JDK中的join,Futrued的实现采用的就是此模式
  • 因为要等待另一方的结果,因此归类到同步类

在这里插入图片描述

实现

模拟t1线程要吃苹果,没有苹果只能等待,等有了苹果之后线程t1就会吃掉苹果。t2线程模拟产生苹果。

1. 非超时版

wait不设置超时时间

@Slf4j
public class TestGuardedSuspension {

    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        new Thread(()->{
            try {
                guardedObject.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardedObject.complete(new Object());
        },"t2").start();


    }

}


@Slf4j
class GuardedObject{
		// 苹果初始值为null
    private Object apple;
    // 锁用于保证吃与生产的同步
    private final static Object lock = new Object();
	//吃苹果,apple == null 时就使用wait方法等待。 apple != null时就吃掉苹果
    public Object get() throws InterruptedException {
        synchronized (lock){
            while (apple == null){
                log.debug("当前还没有苹果");
                lock.wait();
            }
        }
        log.debug("吃掉apple成功");
        return apple;
    }
	// 产生苹果
    public void complete(Object apple){
        synchronized (lock){
            log.debug("投喂苹果");
            this.apple = apple;
            lock.notifyAll();
        }
    }

}

预期结果为t1线程开始等待t2生产苹果,t2线程2s后产生苹果,t1消费苹果。
实际执行结果如下图,可知符合预期。
在这里插入图片描述

2.超时版

wait设置超时时间,假如2s还没有苹果t1线程就退出

@Slf4j
class GuardedObjectTimeOut{

    private Object apple;
    private final static Object lock = new Object();

    public Object get(long millis) throws InterruptedException {
        synchronized (lock){
            // 开始时间
            long begin = System.currentTimeMillis();
            // 已经经历了的时间, 初始为0  , 该变量用于防止假唤醒 重复等待 millis 时间
            long timePass = 0;
            while (apple == null){
                long waitTime = millis - timePass;
                if (waitTime <= 0){
                    log.debug("超时了,不等了,不吃苹果了");
                    break;
                }
                log.debug("当前还没有苹果");
                lock.wait(waitTime);
                timePass = System.currentTimeMillis() - begin;
            }
        }
        if (apple != null){
            log.debug("吃掉apple成功");
        }
        return apple;
    }

    public void complete(Object apple){
        synchronized (lock){
            log.debug("投喂苹果");
            this.apple = apple;
            lock.notifyAll();
        }
    }

}
  1. 测试没有超时代码
    t2线程等待1s
public static void main(String[] args) {
     GuardedObjectTimeOut guardedObject = new GuardedObjectTimeOut();
     new Thread(()->{
         try {
             guardedObject.get(2000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     },"t1").start();

     new Thread(()->{
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         guardedObject.complete(new Object());
     },"t2").start();
 }

测试结果:
在这里插入图片描述
2. 测试超时代码
t2线程等待3s

    public static void main(String[] args) {
        GuardedObjectTimeOut guardedObject = new GuardedObjectTimeOut();
        new Thread(()->{
            try {
                guardedObject.get(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(()->{
            try {
                // 超时
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardedObject.complete(new Object());
        },"t2").start();


    }

测试结果:
在这里插入图片描述
3. 测试假唤醒
测试假唤醒,设置t2线程1s后不投放苹果

    public static void main(String[] args) {
        GuardedObjectTimeOut guardedObject = new GuardedObjectTimeOut();
        new Thread(()->{
            try {
                guardedObject.get(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(()->{
            try {
                // 等待1s
                Thread.sleep(1000);
                // 设置投喂的苹果为null,即为不投放苹果
                guardedObject.complete(null);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t2").start();


    }

测试结果:
在这里插入图片描述

总结

  • 该模式适合于有一个结果需要从一个线程传递到另一个线程的情况下使用
  • 超时版应该注意等待时间需要注意假唤醒的情况,所以应该设置一个经历时间waitpass和一个开始等待的时间begin,然后在wait后面动态的用当前时间减去begin赋值给waitpass历经时间。不这样会出现实际等待时间超过自己设置的等待时间
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值