栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

同步模式之保护性暂停

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

同步模式之保护性暂停

黑马视频讲解: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历经时间。不这样会出现实际等待时间超过自己设置的等待时间
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/919363.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号