一 点睛
多个线程监控某个变量,A 线程监控到共享变量发生变化后即将触发某个动作,但是此时发现另外一个线程 B 已经针对该变量的变化开始了行动,因此 A 便放弃了准备开始的工作,我们把这样的线程间交互称为 Balking(犹豫)设计模式。
其实这样的场景在生活中很常见,比如,你去饭店吃饭,吃到中途中想再点一个小菜,于是你举起手示意服务员,其中一个服务员看到你举手正准备过来的时候,发现距离你比较近的服务员已经准备要受理你的请求,于是中途放弃了。
再比如,我们用 word 编辑文档的时候,每次文字编辑都代表着文档状态发生了变化,除了我们可以使用 ctrl+s 快捷键手动保存以外,word 软件本身也会定期触发自动保存,如果 word 自动保存文档的线程在执行保存动作的时候,恰巧我们进行了主动保存,那么自动保存文档的线程将会放弃此时保存的动作。
简单的说,就是某个线程因为发现其他线程正在进行相同的工作而放弃即将开始的任务。
二 实战
1 文档类
package concurrent.barking;
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;
/**
* @className: Document
* @description: 代表正在编辑的文档类
* @date: 2022/4/24
* @author: cakin
*/
public class Document {
private final FileWriter write;
// 一次需要保存的内容,可以将其理解为内容缓存
private List<String> content = new ArrayList<>();
// 如果文档发生改变,changed 会被设置为 true
private boolean changed = false;
// 自动保存文档的线程
private static AutoSaveThread autoSaveThread;
// 构造时需要传入文档保存的路径和文档的名称
public Document(String documentPath, String documentName) throws IOException {
this.write = 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);
// 文档改变,change 会变为 true
this.changed = true;
}
}
// 文档关闭的时候首先中断自动保存线程,然后关闭 write 释放资源
public void close() throws IOException {
autoSaveThread.interrupt();
write.close();
}
// 认为手动进行文档保存
public synchronized void save() throws IOException {
synchronized (this) {
// balking 设计模式,如果文档已经保存了,则直接返回
if (!changed) {
return;
}
System.out.println(currentThread() + " execute the save action");
// 将内容写入文档中
for (String cacheLine : content) {
this.write.write(cacheLine);
this.write.write("\r\n");
}
this.write.flush();
// 将 changed 修改为 false,表明此刻再没有新的内容编辑
this.changed = false;
}
}
}
2 自动保存文档线程
package concurrent.barking;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
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;
}
}
}
}
3 手动编辑文档线程
package concurrent.barking;
import java.io.IOException;
import java.util.Scanner;
/**
* @className: DocumentEditThread
* @description: 主动编辑文档,除了对文档进行修改之外,还会同时按下 Ctrl + S 组合键(调用 save 方法)主动保存
* @date: 2022/4/24
* @author: cakin
*/
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);
}
}
}
4 测试
package concurrent.barking;
public class Test {
public static void main(String[] args) {
new DocumentEditThread("G:\\", "balking.txt").start();
}
}
三 测试结果
1
Thread[DocumentAutoSaveThread,5,main] execute the save action
2
Thread[DocumentAutoSaveThread,5,main] execute the save action
3
Thread[DocumentAutoSaveThread,5,main] execute the save action
4
Thread[DocumentAutoSaveThread,5,main] execute the save action
5
Thread[DocumentAutoSaveThread,5,main] execute the save action
6
Thread[DocumentEditThread,5,main] execute the save action
7
Thread[DocumentAutoSaveThread,5,main] execute the save action
8
Thread[DocumentAutoSaveThread,5,main] execute the save action
quit
四 说明
可以看到,自动保存线程和手动保存线程在分别进行文档保存,它们并不会在不改变内容的情况下执行 save 动作。