1.概念
多个线程监控某个共享变量,A线程监控到共享变量发生变化后即将触发某个动作,但是此时发现有另外一个线程B已经针对该变量的变化开始了行动,因此A便放弃了准备开始的动作,我们把这样的线程间交互成为Balking(犹豫)设计模式。
2.应用场景
- 其实这样的场景在生活中很常见,比如你去饭店吃饭,吃到途中想要再点一个小菜,于是你举起手示意服务员,其中一个服务员看到了你举手正准备走过来的时候,发现距离你比较近的服务员已经准备要受理你的请求于是中途放弃了。
- 再比如,我们在用word编写文档的时候,每次的文字编辑都代表着文档的状态发生了改变,除了我们可以使用ctrl+s快捷键手动保存以外,word软件本身也会定期触发自动保存,如果word自动保存文档的线程在准备执行保存动作的时候,恰巧我们进行了主动保存,那么自动保存文档的线程将会放弃此次的保存动作。
看了以上两个例子的说明,想必大家已经清楚了Balking设计模式要解决的问题了吧,简短的说就是某个线程因为发现其他线程正在进行相同的工作而放弃即将开始的任务。
在本章中,我们将通过模拟word文档自动保存与手动保存的功能讲解
3.Balking模式的设计与应用
Balking模式之文档编辑
Document:
package MutilThreadModel.BalkingModel;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static java.lang.Thread.currentThread;
/**
* Created by JYM on 2019/1/14
* 在Document中有两个主要方法save和edit分别用于保存文档和编辑文档。
* */
//代表正在编辑的文档类
public class Document
{
//如果文档发生改变,changed会被设置为true
private boolean changed = false;
//一次需要保存的内容,可以将其理解为内容缓存
private List<String> content = new ArrayList<>();
private final FileWriter writer;
//自动保存文档的线程
private static AutoSaveThread autoSaveThread;
//构造函数需要传入文档保存的路径和文档名称
private Document(String documentPath,String documentName) throws IOException
{
this.writer = new FileWriter(new File(documentPath,documentName),true);
}
//静态方法,主要用于创建文档,顺便启动自动保存文档的线程
public static Document create(String documentPath,String documentName) throws IOException
{
Document document = new Document(documentPath,documentName);
autoSaveThread = new AutoSaveThread(document);
autoSaveThread.start();
return document;
}
//文档的编辑,其实就是往content队列中提交字符串
public void edit(String content)
{
synchronized (this)
{
this.content.add(content);
//文档改变,changed会变为true
this.changed = true;
}
}
//文档关闭的时候首先中断自动保存线程,然后关闭writer释放资源
public void close() throws IOException
{
autoSaveThread.interrupt();
writer.close();
}
//save方法用于为外部显式进行文档保存
public void save() throws IOException
{
synchronized (this)
{
//balking,如果文档已经被保存了,则直接返回
if (!changed)
{
return;
}
System.out.println(currentThread()+" execute the save action");
//将内容写入文档中
for (String cacheLine : content)
{
this.writer.write(cacheLine);
this.writer.write("\r\n");
}
this.writer.flush();
//将changed修改为false,表明此刻再没有新的内容编辑
this.changed = false;
this.content.clear();
}
}
}
在上面的代码中:
- edit方法和save方法进行方法同步,其目的在于防止当文档在保存的过程中如果遇到新的内容被编辑时引起的共享资源冲突问题。
- changed在默认的情况下为false,当有新的内容被编辑的时候将被修改为true。
- 在进行文档保存的时候,首先查看changed是否为true,如果文档发生过编辑则在文档中保存新的内容,否则就会放弃此次保存动作,changed是balking pattern关注的状态,当changed为true的时候就想远处的服务员看到客户的请求被另外一个服务员接管了一样,于是放弃了任务的执行。
- 在创建Document的时候,顺便还会启动自动保存文档的线程,该线程的主要目的在于在固定时间里执行 一次文档保存动作。
AutoSaveThread
package MutilThreadModel.BalkingModel;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* Created by JYM on 2019/1/14
* 下面这个类函数实现的是文档的自动保存.
* AutoSaveThread比较简单,其主要的工作就是每个一秒的时间调用一次document的save方法。
* */
public class AutoSaveThread extends Thread
{
private final Document document;
public AutoSaveThread(Document document)
{
super("DocumentAutoSaveThread");
this.document = document;
}
@Override
public void run()
{
while (true)
{
try
{
//每隔一秒自动保存一次文档
document.save();
TimeUnit.SECONDS.sleep(1);
}catch (IOException | InterruptedException e)
{
break;
}
}
}
}
DocumentEditThread:
package MutilThreadModel.BalkingModel;
import java.io.IOException;
import java.util.Scanner;
/**
* Created by JYM on 2019/1/14
* DocumentEditThread线程则类似于主动编辑文档的作者,在DocumentEditThreadzhong中除了对文档进行修改编辑之外,还是同时按下Ctrl+S
* 组合键(调用save方法)主动保存。
* */
//该线程代表的是主动进行文件编辑的线程,为了增加交互性,我们使用了Scanner
public class DocumentEditThread extends Thread
{
private final String documentPath;
private final String documentName;
private final Scanner scanner = new Scanner(System.in);
public DocumentEditThread(String documentPath,String documentName)
{
super("DocumentEditThread");
this.documentPath = documentPath;
this.documentName = documentName;
}
@Override
public void run() {
int times = 0;
try {
Document document = Document.create(documentPath,documentName); //创建编辑文档
while (true)
{
//获取用户的键盘输入
String text = scanner.next();
if ("quit".equals(text))
{
document.close();
break;
}
//将内容编辑到document中
document.edit(text);
if (times==5)
{
//用户在输入了5次之后进行文档保存
document.save();
times=0;
}
times++;
}
}catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
/**
- DocumentEditThread类代表了主动编辑文档的线程,在该线程中,我们使用scanner交互的方式,每一次对文档的修改
- 都不可能直接保存(Ctrl+S),因此在程序中约定了五次以后主动执行保存动作,当输入quit时,表示要退出此次文档编辑。
- */
测试:
package MutilThreadModel.BalkingModel;
/**
* Created by JYM on 2019/1/14
*
* 测试程序:
* */
public class BalkingTest
{
public static void main(String[] args)
{
new DocumentEditThread("G:\\","balking.txt").start();
}
}
/**
* Balking模式在日常的开发中很常见,比如在系统资源的加载或者某些数据的初始化时,
* 在整个系统中声明中期中资源可能只被加载一次,我们就可以采用balking模式加以解决,
* 代码如下:
* public synchronized Map<String, Resource> load()
* {
* //balking
* if(load)
* {
* return resourceMap;
* }else
* {
* //do the resource load task;
* //....
* this.loaded = true;
* return resoureMap;
* }
* }
* */