什么是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放入临界区间。