缓存主要做用是提升应用程序吞吐量和响应性,固然也有负面影响,占用更多内存。在设计SqlTemplate也有个简单的本地缓存,sql模板实际只须要解释一次就能够了,之后的调用复用以前解释过。开始的时候是使用简单的HashMap实现的,可是在并发状况下会出现重复解释,下面是初版的代码片断。 java
public class Configuration { private ConcurrentHashMap<String, SqlTemplate > templateCache; ...... public SqlTemplate getTemplate(final String content) { if (cacheTemplate) { //是 不然缓存模板 SqlTemplate sqlTemplate = templateCache.get(content); if (sqlTemplate != null){ sqlTemplate = createTemplate(content) ; templateCache.put(content, sqlTemplate) ; } return sqlTemplate; } return createTemplate(content); } //解释构建模板对象 private SqlTemplate createTemplate(String content) { SqlTemplate template = new SqlTemplate.SqlTemplateBuilder(this, content) .build(); return template; } ...... }
经过看上面的代码,很容易发现问题的所在,一个线进createTemplate解释模板,其余线程不知道这个模板已经在解释中,因此可能会出现同sql模板会出现屡次计算。理想的状况下其余线程知道模板在解释中,只需等待完成。下面是使用FutureTask改进后的第二版本代码,看似很完美^_^。 sql
public class Configuration { private ConcurrentHashMap<String, FutureTask<SqlTemplate>> templateCache; ...... public SqlTemplate getTemplate(final String content) { if (cacheTemplate) {////是 不然缓存模板 FutureTask<SqlTemplate> f = templateCache.get(content); if (f == null) { FutureTask<SqlTemplate> ft = new FutureTask<SqlTemplate>( new Callable<SqlTemplate>() { public SqlTemplate call() throws Exception { return createTemplate(content); } }); f = templateCache.putIfAbsent(content, ft); if (f == null) { ft.run(); f = ft; } } try { return f.get(); } catch (Exception e) { templateCache.remove(content); //防止缓存污染 throw new RuntimeException(e); } } return createTemplate(content); } //解释构建模板对象 private SqlTemplate createTemplate(String content) { SqlTemplate template = new SqlTemplate.SqlTemplateBuilder(this, content) .build(); return template; } ...... }
第二版解决了初版的缺陷(重复解释),并发性也不错,能很好返回已经解释过的结果。此次缓存不是结果值,而是一个FutureTask,关于Future详细使用请参考啊了个里写的《Java 并发之 Future 接口》。关于SqlTemplate请参考http://my.oschina.net/u/866190/blog/175800 缓存