Balking Pattern

什么是Balking Pattern?

我现在在餐厅吃饭,考虑想要吃什么东西,举手示意服务生。这个时候服务生也看到了我,但是她看到了有另一个服务生靠近了我,所以就不过来了。

当前不适合这个操作,或者没有必要进行这个操作,就直接放弃这个操作回去,这就是Balking Pattern。

现在设想一个场景,某个文件,一个线程SaverThread如果发现文件没有被保存每隔1s就自动保存一次,另一个线程Changer对文件做出修改,修改完如果文件没有保存就保存一次。

首先实现Data类:

package balkPattern;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Data {
    private String fileName;//文件名称
    private String content;//文件内容
    private boolean changed;//判断是否改变过 没储存设置为false
    
    public Data(String fileName,String content){
        this.fileName = fileName ;
        this.content = content;
        this.changed = true;//初始化为应该保存
    }
    public synchronized void change(String newContent){
        this.content = newContent;
        this.changed = true;//未保存
    }
    public synchronized void save(){
        if(!changed){//没有改变
            System.out.println("因为文件没有改变,"+Thread.currentThread().getName()+"选择放弃保存");
            return;//不用改 直接return
        }
        try {
            doSave();//去做保存
            changed = false;//是否被修改置为false
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private synchronized void doSave() throws IOException{
        System.out.println(Thread.currentThread().getName()+"想要保存,"+"保存成功,保存内容为"+content);
        Writer writer = new FileWriter(fileName);
        writer.write(content);
        writer.close();
    }
}


然后实现ChangerThread类:

package balkPattern;

public class ChangerThread implements Runnable {
    private Data data;
    public ChangerThread(Data data){
        this.data = data;
    }
    @Override
    public void run() {
        int i = 0;
        while(true){//循环保存
            data.change("编号:"+ i++);
            try {
                Thread.sleep(1000);//休息1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data.save();//主动存储
        }
    }
}

实现SaverThread类:

package balkPattern;

public class SaverThread implements Runnable {
    private Data data;
    public SaverThread(Data data){
        this.data = data;
    }
    @Override
    public void run(){
        while(true){
            data.save();//保存
            try {
                Thread.sleep(1000);//休息1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

最后写一个测试类Test:

package balkPattern;

public class Test {
    public static void main(String[] args) {
        Data data = new Data("C:\\Users\\m1834\\Desktop\\test.txt", "空");
        ChangerThread ct = new ChangerThread(data);
        SaverThread st = new SaverThread(data);
        new Thread(ct,"changer").start();
        new Thread(st,"saver").start();
    }
}

然我们现在来回头看代码,对于这个文件,两个线程都想保存,然而无论哪个线程想保存都是没有重复保存的,当content内容相同的时候,线程就会balk住,而不会调用doSave方法。


看看Balking Pattern的所有参与者

1、GuardedObject(被警戒的对象)参与者

guardedObject参与者是一个拥有被警戒方法(guardedMethod)的类,当线程执行guardMethod时,只要满足警戒条件,就会执行实例的操作,当警戒条件不成立时,就会直接退出。警戒条件成立与否,会随着GuardedObject参与者的状态变化而变化。


当防卫条件不成立时,线程不可以执行guardedMethod的目的操作,而会直接离开(balk)。

当防卫条件成立时,线程可以执行guardedMethod的目的操作。


什么时候使用Balk Pattern?

1、不需要刻意去执行的时候。比如示例中当content字段没有改变的时候,就在save方法里balk住了。这样balk可以有效提高程序的执行效率。

2、不想等待警戒条件成立。和guarded suspension pattern不一样,一旦不满足警戒条件,balk pattern不满足警戒条件就会立刻退出,这可以有效提高程序的响应度。

3、警戒条件只有第一次成立时。比如某个初始化方法,调用初始化方法的时候,会先检查字段,如果已经被初始化了,所以就直接return了(balk)。比如下面:

public class Something{
    private boolean initialized = false;
    public synchronized void init(){
        if(initialized){
            return;
        }
        doInit();//真正初始化
        initialized = true;
    }
    private void doInit(){
        //实际的初始化操作
    }
}

在这种情况下就应该使用if表达式来检查警戒条件:

if(initialized){
    return;
}
如果使用Guarded Suspension Pattern,当然就错了:

while(initialized){
    wait();
}
因为initialized字段只要变成true就再也不会变成false了。对于这样的情况我们就应该使用Balk Pattern。


如何通知给调用者balk结果?

1、忽略balk的发生,比如范例,不告诉调用者发生了balk,而是直接return;结束

2、使用boolean返回值通知balk的发生,true就是没有balk的发生,返回false就是发生了balk,目标操作没有发生,当然在返回值为对象的时候也可以返回null表示发生了balk。

3、使用异常表示发生了balk。


什么时候结束wait?

1、notify/notifyAll,前面文章说得太多,不说了

2、interrupt方法被执行,被interrupt时,wait set里的线程会重新获取obj的锁定(和notify/notifyAll时一样),然后抛出InterruptedException异常。notify/notifyAll方法是对实例调用的,而interrupt方法是对线程调用的。

3、timeout。当wait方法参数中设置timeout时间到了的时候,与notify/notifyAll方法一样,需要重新获取obj的锁。

送一我们没有办法直到wait方法到底是被notify/notifyAll的还是timeout了。然而我们可以使用guarded timed解决这个问题。


guarded timed的实现

1、首先定义timeout用的异常TimeoutException,我们将TimeoutException声明成InterruptedException的子类,也就是我们将timeout视为取消的一种。

public class TimeoutException extends InterruptedException{
    public TimeoutException(String msg){
        super(msg);
    }
}
2、写具有timeout的Host类。

public class Host{
    private final logn timeout;//timeout值
    private boolean ready =false;//如果可以执行方法的话为true
    public Host(long timeout){
        this.timeout = timeout;
    }

    //更改状态
    public synchronized void setExecutable(boolean on){
        ready = on;
        notifyAll();
    }
    //评判状态后执行它
    public synchronized void execute() throws InterruptedException, TimeoutException{
        long start = System.currentTimeMillis();//开始时刻
        while(!ready){
            long now = System.currentTimeMillis();//现在时间
            long rest = timeout - (out - start);//剩下的等待时间
            if(rest<=0){
                throw new TimeoutException("超时");
            } 
            wait(rest);
        }
        doExecute();
    }
    //实际的处理操作
    private void doExecute(){
        System.out.println(Thread.currentThread().getName()+"calls doExecute");
    }
}
3、写测试了类Test。

public class Test{
    public static void main(String[] args){
        Host host = new Host(10000);
        try{
            System.out.println("开始执行");
            host.execute();
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

在测试类Test中,设置timeout为10000ms,建立Host的实例,调用Host的execute方法,如果故意不调用setExecutable(true)的话,就会timeout。


synchronized处被阻挡的状态和wait进入等待区的区别

思考以下两种情况:

1、synchronized处想要获得锁,但是被阻挡

2、执行wait而进入等待区(wait set)

对于1:

无法使得1状态的线程timeout。因为无论是synchronized方法或者synchronized块,都没有办法设置timeout的值。

对于状态1中的线程调用interrupt,也没有办法抛出InterruptedException异常。而必须再获取锁,进入synchronized了以后,调用wait、sleep、join等会意识到现在是不是中断状态的方法,或者使用isInterrupted方法或interrupted方法自己检查是不是中断状态,自己throw出去。

对于2:

对于状态2中,则可以timeout,而且对于状态2中调用interrupt的话,会抛出InterruptedException异常。


总结

Java语言中,使用if语句来进行警戒条件测试。balk的时候,可以使用return方法退出,或者throw抛出异常。警戒条件的测试,要使用synchronized放入临界区间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值