1.什么是Balking设计

多个线程监控某个共享变量,A线程监控到共享变量发生变化后即将触发某个动作,但是此时发现有另外一个线程B 已经针对该变量的变化开始了行动,因此A便放弃了准备开始的工作, 我们把这样的线程间交互称为Balking(犹豫) 设计模式。其实这样的场景在生活中很常见,比如你去饭店吃饭,吃到途中想要再点一个小菜,于是你举起手示意服务员(见图22-1),其中一个服务员看到了你举手正准备走过来的时候,发现距离你比较近的服务员已经准备要受理你的请求于是中途放弃了。

2.Balking模式之文档编辑

2.1 Document

  1. import java.io.File;
  2. import java.io.FileWriter;
  3. import java.io.IOException;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. // 代表正在编辑的文档类
  7. public class Document {
  8. // 如果文档发生改变,changed会被设置为true;
  9. private boolean changed = false;
  10. // 一次需要保存的内容,可以将其理解为内容缓存
  11. private List<String> content = new ArrayList<>();
  12. private final FileWriter writer;
  13. // 自动保存文档的线程
  14. private static AutoSaveThread autoSaveThread;
  15. // 构造函数需要传入文档保存的路径和文档名称
  16. private Document(String documentPath, String documentName) throws IOException {
  17. this.writer = new FileWriter(new File(documentPath, documentName));
  18. }
  19. // 静态方法,主要用于创建文档,顺便启动自动保存文档的线程
  20. private static Document create(String documentPath, String documentName) throws IOException {
  21. Document document = new Document(documentPath, documentName);
  22. autoSaveThread = new AutoSaveThread(document);
  23. autoSaveThread.start();
  24. return document;
  25. }
  26. // 文档编辑,其实就是往document队列中提交字符串
  27. public void edit(String content) {
  28. synchronized (this) {
  29. this.content.add(content);
  30. // 文档改变,changed会变为true
  31. this.changed = true;
  32. }
  33. }
  34. // 文档关闭的时候,首先中断自动保存线程,然后关闭writer释放资源
  35. public void close() throws IOException {
  36. autoSaveThread.interrupt();
  37. writer.close();
  38. }
  39. // save 方法用于为外部显示进行文档保存
  40. public void save() throws IOException {
  41. synchronized (this) {
  42. // balking, 如果文档已经保存了,则直接返回
  43. if ( !changed ) {
  44. return;
  45. }
  46. System.out.println(Thread.currentThread() + " execute the save action");
  47. for(String cacheLine: content) {
  48. this.writer.write(cacheLine);
  49. this.writer.write("\r\n");
  50. }
  51. this.writer.flush();
  52. this.changed = false;
  53. this.content.clear();
  54. }
  55. }
  56. }

在上述代码中:

  • edit方法和save方法进行方法同步, 其目的在于防止当文档在保存的过程中如果遇到新的内容被编辑时引起的共享资源冲突问题。
  • changed在默认情况下为false, 当有新的内容被编辑的时候将被修改为true。
  • 在进行文档保存的时候, 首先查看changed是否为true, 如果文档发生过编辑则在文档中保存新的内容, 否则就会放弃此次保存动作, changed是balking pattern关注的状态, 当changed为true的时候就像远处的服务员看到客户的请求被另外一个服务员接管了一样,于是放弃了任务的执行。
  • 在创建Document的时候, 顺便还会启动自动保存文档的线程, 该线程的主要目的在于在固定时间里执行一次文档保存动作。

2.2 AutoSaveThread

  1. import java.io.IOException;
  2. import java.util.concurrent.TimeUnit;
  3. public class AutoSaveThread extends Thread{
  4. private final Document document;
  5. public AutoSaveThread(Document document) {
  6. super("DocumentAutoSaveThread");
  7. this.document = document;
  8. }
  9. @Override
  10. public void run() {
  11. while(true) {
  12. try {
  13. document.save();
  14. TimeUnit.SECONDS.sleep(1);
  15. } catch (IOException | InterruptedException e) {
  16. e.printStackTrace();
  17. break;
  18. }
  19. }
  20. }
  21. }

2.3 DocumentEditThread

AutoSave Thread线程用于文档自动保存, 那么DocumentEd it Thread线程则类似于主动编辑文档的作者, 在DocumentEd it Thread中除了对文档进行修改编辑之外, 还会同时按下Ctrl+S组合键(调用save方法) 主动保存, 见代码清单22-3。

  1. import java.io.IOException;
  2. import java.util.Scanner;
  3. public class DocumentEditThread extends Thread{
  4. private final String documentPath;
  5. private final String documentName;
  6. private final Scanner scanner = new Scanner(System.in);
  7. DocumentEditThread(String documentPath, String documentName) {
  8. super("D");
  9. this.documentPath = documentPath;
  10. this.documentName = documentName;
  11. }
  12. @Override
  13. public void run() {
  14. int times = 0;
  15. try {
  16. Document document = Document.create(documentPath, documentName);
  17. while(true) {
  18. // 获取用户的键盘输入
  19. String text = scanner.next();
  20. if ( "quit".equals(text) ) {
  21. document.close();
  22. break;
  23. }
  24. // 将内容编辑到document中
  25. document.edit(text);
  26. if( times == 5) {
  27. document.save();
  28. times = 0;
  29. }
  30. times++;
  31. }
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. throw new RuntimeException(e);
  35. }
  36. }
  37. }

资源文件加载
image.png