同步模式之保护性暂停

目录

保护性暂停-定义

保护性暂停-实现

保护性暂停-拓展-增加超时


保护性暂停-定义

Guarded Suspension,用一个线程等待另外一个线程的执行结果。

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

保护性暂停作为一种设计模式,其关键点在于GuardedObject,它有一个response的属性,t1想要获取它,需要等待,直到t2得到它时,将response返回给GuardedObject,再由GuardedObject返回给t1。

在这里需要用到线程 的wait()和notify()方法来完成代码编写。

保护性暂停-实现

编写一个GuardedObject类作为这样一个中间桥梁,成员变量 response。一个方法get()用于等待获得结果response,一个方法complete用于返回结果response,并叫醒正在等待的线程。

get()方法因为要等待response直到response不为空为止,如果为空就要继续等待。因此用了while条件判断来防止虚假唤醒,因为complete()唤醒线程的方式是notifyAll(),这样可能会导致虚假唤醒(比如唤醒以后并没有拿到response)

class GuardedObject {
    private Object response;

    public Object get() {
        synchronized (this) {
            //为了防止虚假唤醒 使用while条件判断 直到response不会空时才打破循环
            while (response == null) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

    // 产生结果
    public void complete(Object response) {
        synchronized (this) {
            // 给结果成员变量赋值
            this.response = response;
            // 得到结果 叫醒wait的线程
            this.notifyAll();
        }
    }
}

编写两个线程,一个去等待response,一个去返回response。

t1通过guardedObject.get()方法的等待response返回,t2通过一个donwload方法得到一个list,将list作为response传递给guardedObject.complete()方法。

 public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        new Thread(() -> {
            log.debug("{}等待结果", Thread.currentThread().getName());
            // 等待guardedObject的response
            List<String> list = (List<String>) guardedObject.get();
            log.debug("{}得到结果:{}", list.size());
        }, "t1").start();

        new Thread(() -> {
            try {
                log.debug("{}执行下载", Thread.currentThread().getName());
                List<String> list = Downloader.download();
                // 将下载结果返回给guardedObject的complate的方法
                guardedObject.complete(list);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }, "t2").start();
    }

这就是在两个线程之间交互结果的模式,原来使用join()来交换结果,需要等待线程1执行结束。而保护性暂停可以让t2在执行下载以后还能干一些别的事,不一定要等t1结束,这也是join()方法的局限性,并且join()等待结果的变量必须是全局的,而不是像保护性暂停的模式都是局部变量。

保护性暂停-拓展-增加超时

在上面举例了保护性暂停的代码实现,线程t2用一个donwload()方法去得到一个list作为response传递给guardedObject.complete()。

假设donwload()有很慢的情况,也就是存在超时情况,t1线程就会一直循环等待,我们需要为超时的情况考虑。

目前 get() 方法只要没有拿到response,就会一直循环等待,需要为它增加一个超时判断。

为get()方法增加一个时间参数timeout,当超过这个时间,就不再等待。

但这个timeout是加在wait()方法上吗?假设这个超时时间是2秒,在wait(2)以后,仍然在循环中,因为response还是为null嘛,只是从一直等待变成了等2秒以后继续等待2秒+++++

因此超时时间要这样设置:

在等待之前,设计一个开始时间,在循环结束之前,设计一个经历时间。

    // timeout 表示最大的等待时间(即超时时间)
    public Object get(long timeout) {
        long startTime = System.currentTimeMillis();
        synchronized (this) {
            // 经历的时间
            long passtime = 0;
            //为了防止虚假唤醒 使用while条件判断 直到response不会空时才打破循环
            while (response == null) {
                // 经历的时间大于等于超时时间 则跳出循环
                if (passtime >= timeout) {
                    break;
                }
                try {
                    this.wait(timeout);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 开始时间 - 当前时间 = 经历的时间
                passtime = startTime - System.currentTimeMillis();
            }
            return response;
        }
    }

假设最大的等待时间为2s,开始时间为15:00,一开始经历时间为0,在第一次循环结束的时候是15:02,因此经历的时间就是2s。

如果经历的时间大于等于最大的等待的时间,则跳出循环,当代码走到if判断时,经历时间等于了超时时间,条件成立,则跳出了循环。

还有一个很关键的问题就是,this.wait()方法里究竟放的是哪个时间参数?

刚刚假设的例子是2s,即timeout变量的值为2s,那么this.wait()是放入timeout的这个变量吗?就比如说,现在我们的经历时间是1s,走到if判断那里,条件不成立,那么还需要调用一次this.wait(),已经经历过1s了,虽然timeout的设定为2s,但是如果wait()里写的timeout这个变量,则又要等2s,这是不对的。正确的方式应该是 timeout - passtime ,this.wait()只需要再等待1s就好。

保护性暂停的模式理解起来并不难,但是在增加超时的拓展时,对于超时时间的设计让我觉得耳目一新,醍醐灌顶的感觉,因此写了博客记录学习笔记。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值