Guarded Suspension Pattern

什么是Guarded Suspension Pattern?

设想一个场景,你在家里换衣服,突然门铃响了,总不能换到一半出去开门吧,于是说:“请稍等”。换完衣服再把房门打开。

当现在不适合马上执行某一个操作的时候,就需要这个想要执行操作的线程进行等待。

这里我们用代码来说明一切:

我们模拟4个人存钱取钱的场景,银行初始金额为0。

package guardedSuspension;

public class Bank {
    private int money = 0;
    public Bank(){
    }
    public synchronized void withDraw(int money){
        while(this.money-money<0){
            try {
                wait();//只要钱不够 就不能进入临界区
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //开始取钱
        this.money-=money;
        System.out.println(Thread.currentThread().getName()+"取钱"+money+",还剩"+this.money);
        notifyAll();//唤醒所有线程
    }
    public synchronized void save(int money){
        this.money+=money;//存钱
        System.out.println(Thread.currentThread().getName()+"存了钱"+money+",现在银行还剩"+this.money+"元");
    }
}

然后是实现了Runnable的target:

package guardedSuspension;

import java.util.Random;

public class PersonRunnable implements Runnable {

    private Bank bank;
    PersonRunnable(Bank bank){
        this.bank = bank;
    }
    @Override
    public void run() {
        Random random = new Random();
        int money = random.nextInt(10);//0-9随机数
        bank.save(money);//存钱
        int money2 = random.nextInt(10);//0-9随机数
        bank.withDraw(money2);//取钱
    }

}
最后写一个测试类:

package guardedSuspension;

public class Test {
    public static void main(String[] args) {
        Bank bank = new Bank();
        PersonRunnable t1 = new PersonRunnable(bank);
        new Thread(t1,"Diana").start();
        new Thread(t1,"Tommy").start();
        new Thread(t1,"Ginna").start();
        new Thread(t1,"Jerry").start();
    }
}
下面是一种运行结果:


现在我们开始来看代码,对于withDraw方法,存在着一个“警戒条件”,也就是只有取钱完money仍然大于0才能执行。

当线程抵达代码段while(this.money-money>0)时,会分成满足警戒条件和不满足警戒条件两种情况,警戒条件(this.money-money>=0)成立时,代码不会进入while块,而是直接进入下一个语句,直接取钱,而不会wait,警戒条件不成立,才会执行wait。

执行wait后线程等着被notify/notifyAll,然而更根本的时,线程在等待警戒条件的成立,等到收到notify/notifyAll时再判断警戒条件成立不成立,如果成立就取钱,否则继续进入wait set。这就是为什么有时候(比如上方的运行结果)程序迟迟不会结束的原因,因为所有人都存钱完了,然后还是不够取,所以那个没有足够钱取的线程就一直呆在wait set了。

整理一下,关于警戒条件的架构使用方法是:

while("警戒条件"的逻辑否定){
    使用wait等待
}
进行"目的操作"

该模式的时序图如下:



如何理解Guarded Suspension Pattern?

1、有条件的synchronized,在Single Threaded Execution的基础上,只要有一个线程进入临界区间,其他线程就不能进入,而是等待。因此可以称为有条件的synchronized。

2、多线程版的if。多个线程不符合警戒条件就不执行,否则就执行。


忘记更改状态和生命性

wait中的线程会在被notify/notifyAll的时候重新测试警戒条件,如果始终达到不了警戒条件则依然会进入wait set。

如果忘了修改GuardedObject参与者(比如上面的money)的状态,会导致警戒条件永远无法成立,使得线程无法继续前进,使得程序失去生命性。


各种实现方法

1、guarded wait,就像本例的实现,使用wait等待,直到被notify/notifyAll后再次测试警戒条件。使用wait等待的时间,其实就是停止在等待区里停止执行,所以不会浪费Java执行环境的处理时间:

//等待端的范例
while(!ready){
    wait();
}

//唤醒端的范例
ready = true;
notifyAll();

2、busy wait(忙等待),线程不使用wait方法等待,而是使用yield方法(把当前运行机会交给优先级高的线程),然后不断测试条件的实现方法。因此等待中的线程还是运行这,会浪费jvm的时间。yield是Thread的类方法,这个方法不会解除锁,所以这个方法不应该写在synchronized里,否则容易死锁。yield只是放弃当前cpu的使用,让给其他线程,当前线程置为可运行状态。也就是说虽然当前线程调用了yield,还是可能接下来是自己运行,正确示例:

//等待端的范例
while(!ready){
    Thread.yield();
}
//唤醒端的范例
ready = true;
//没有notify/notifyAll

3、spin lock(自旋锁)。旋转而锁定的意思,表现出条件成立前while循环不断旋转的样子。spin lock有时候意思和guarded wait相同,有时候和guarded wait相同。有时候一开始是busy wait,之后换成guarded wait的方式。另外,有些以硬件实现的同步机制,也称为spin lock。


4、polling(进行调查)。反复检查某个事件是否发生,当发生时,就进行对应处理的方式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值