Java高并发 - 22.Balking设计模式

多个线程监控某个共享变量,A线程监控到共享变量发生变化后即将触发某个动作,但是此时发现有另外一个线程B已经针对该变量的变化开始了行动,A便放弃了准备开始的工作。我们把这样的线程间交互称为Balking(犹豫)设计模式。

比如,我们在用word编写文档的时候,每次的文字编辑都代表着文档的状态发生了变化,除了我们可以手动保存为,word本身也会定期触发自动保存。如果word自动保存文档的线程在准备执行保存动作的时候,恰巧我们进行了主动保存,那么自动保存文档的线程将会放弃此次保存操作。

Document

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));
	}
	
	// 静态方法,主要用于创建文档,顺便启动自动保存文档的线程
	public static Document create(String documentPath, String documentName)
			throws IOExceptioin {
		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();
	}
	
	// 显式保存文档
	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();
		}
	}
}

AutoSaveThread

AutoSaveThread用于定时保存文档。

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;
			}
		}
	}
}

总结

Balking模式在日常开发中很常见,比如系统资源的加载或者某些数据的初始化。在整个系统生命周期中资源可能只被加载一次,可以采用balking模式加以解决。

	public synchronized Map<String, Resource> load() {
		// balking
		if (loaded) {
			return resourceMap;
		} else {
			// do the resource load task
			// ...
			this.loaded = true;
			return resourceMap;
		}
	}