锁定老帖子 主题:缓存同步策略重构
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-11-24
我想错了
|
|
返回顶楼 | |
发表时间:2007-11-24
Quake Wang 写道 javatar 写道 Quake Wang 写道 4. 若已更改,清除缓存中对于的这个Key,并且跳到第一步 5. 若不存在,重新解析模板并将模板压入缓存 第5步会不会出现并发修改异常? 这依赖于Cache的实现,成熟的Cache解决方案都会在put方法内部解决并发问题。 正解。内存缓存,用一个同步的HashMap就可以了,譬如继承LinkedHashMap,重写方法removeEldestEntry便可以实现一个LRU策略的Map,够成熟。 |
|
返回顶楼 | |
发表时间:2007-11-24
nihongye 写道 思路挺好,代码有问题。 syn..(globalLock) syn..(resourceLock ){ 之间未同步。不用同步块的形式 恩,只是我想:两个块之间还需要同步吗? 第一个同步块确保取到资源对应的唯一锁。 第二个同步块确保同一的资源不产生多个实例。 再第二个同步块里还有一次读取操作,我还没想到需要在两块之间同步的理由。 |
|
返回顶楼 | |
发表时间:2007-11-24
jindw 写道 nihongye 写道 思路挺好,代码有问题。 syn..(globalLock) syn..(resourceLock ){ 之间未同步。不用同步块的形式 恩,只是我想:两个块之间还需要同步吗? 第一个同步块确保取到资源对应的唯一锁。 第二个同步块确保同一的资源不产生多个实例。 再第二个同步块里还有一次读取操作,我还没想到需要在两块之间同步的理由。 不用,是我搞错了:) |
|
返回顶楼 | |
发表时间:2007-11-25
发布了新版本0.7.3:http://www.commontemplate.org
采用了leadyu的方式,使用外包装分级锁。 org.commontemplate.engine.TemplateFactoryImpl 的相关代码: private final Cache templateCache; // 策略传入 private final ReloadController reloadController; // 策略传入 private final TemplateSourceComparator templateSourceComparator; // 策略传入 private final Object factoryLock = new Object(); private static final class TemplateLock { TemplateLock() {} private Template template = null; Template getTemplate() { return template; } void setTemplate(Template template) { this.template = template; } } private Template cache(String name, String encoding) throws IOException, ParseException { TemplateLock templateLock = null; // 工厂锁,锁定模板锁 synchronized(factoryLock) { templateLock = (TemplateLock)templateCache.get(name); if(templateLock == null){ templateLock = new TemplateLock(); templateCache.put(name, templateLock); } assert(templateLock != null); // 后验条件 } // 模板锁,锁定模板获取过程 synchronized (templateLock) { Template template = templateLock.getTemplate(); if (template == null) { // 不存在,解析加载 TemplateSource templateSource = load(name, encoding); template = parse(templateSource); templateLock.setTemplate(template); } else { // 已存在,检查热加载 if (reloadController != null && reloadController.shouldReload(name)) { // 是否需要检查热加载 TemplateSource templateSource = load(name, encoding); if (templateSourceComparator != null && templateSourceComparator.isModified(template, templateSource)) { // 比较是否已更改 template = parse(templateSource); templateLock.setTemplate(template); return template; } } } assert(template != null); // 后验条件 return template; } } |
|
返回顶楼 | |
发表时间:2007-11-27
是否应像jindw那样加入无锁预读?那就需要两次热加载检查,如:
private Template cache(String name, String encoding) throws IOException, ParseException { // 先进行一次无锁读取,此过程不调用任何有副作用(修改状态)的函数 TemplateLock templateLock = (TemplateLock)templateCache.get(name); if (templateLock != null && templateLock.getTemplate() != null) { Template template = templateLock.getTemplate(); if (reloadController != null && reloadController.shouldReload(name)) { // 是否需要检查热加载 TemplateSource templateSource = load(name, encoding); if (templateSourceComparator != null && templateSourceComparator.isModified(template, templateSource)) { // 比较是否已更改 template = null; } } if (template != null) return template; } // 工厂锁,锁定模板锁 synchronized(factoryLock) { TemplateLock templateLock = (TemplateLock)templateCache.get(name); // 此处应保持重新读取 // 同上帖... } // 模板锁,锁定模板获取过程 synchronized (templateLock) { // 同上帖... } } 还有没有更好的方式? 如果没问题,准备下一个版本加入。 |
|
返回顶楼 | |
发表时间:2007-11-27
这样不行,锁得的就是Load方法,还放在外面无锁预读,不是白锁了吗?(见上第9行),虽然说,两个线程同时Load没问题,只要Put的时候同步一下get就可以了,但是,大并发下,多个线程,同时Load一个资源,做同样的比较,不是太浪费了吗?这样锁得目的,两个:1)是保证线程安全,因为无法知道parse语法分析时会不会涉及一些共享资源
2)保证需要加载资源时,其他访问线程可以先等着,不要也去加载,浪费CPU和IO操作,load是非常慢的一个操作。 而且上面还有一个问题,就是热加载。每次访问都去load一下是不行的,load是一个秒级操作,对于系统级的组件来说(比如common template or JSP)这是绝对不能接受的。 |
|
返回顶楼 | |
发表时间:2007-11-27
leadyu 写道 2)保证需要加载资源时,其他访问线程可以先等着,不要也去加载,浪费CPU和IO操作,load是非常慢的一个操作。
嗯,这是个问题,看来预读时要避开IO读写等耗时操作才行。 如果没有热加载问题,进行预读是很好的想法,如: ... TemplateLock templateLock = (TemplateLock)templateCache.get(name); if (templateLock != null && templateLock.getTemplate() != null) { return templateLock.getTemplate() } ... 另外,IO操作出现在第10行,而不是第9行,因为: 上面的load是回调TemplateSourceLoader接口的loadTemplateSource(), TemplateSourceLoader.loadTemplateSource通常不是真的去加载数据,而是简单的创建表示信息, 以FileTemplateSourceLoader为例,当调用loadTemplateSource时,只是将new File()包装成TemplateSource返回,并没有打开流,应该不会有大的性能消耗。 当调用TemplateSource的getReader()或getLastModified()才有可能打开流或读取内容, 也就是第10行:templateSourceComparator.isModified(template, templateSource)) 可能出现大的性能消耗,isModified可能比的是最后修改时间,也可能是全文对比,要看TemplateSourceComparator接口的具体实现,但不管怎么样,即然要拿到文件的实际信息,此时总会有IO操作。 |
|
返回顶楼 | |
发表时间:2007-11-27
上面第9第10行可以去掉,多余了。
引用 而且上面还有一个问题,就是热加载。每次访问都去load一下是不行的,load是一个秒级操作,对于系统级的组件来说(比如common template or JSP)这是绝对不能接受的。
我暂时收回这句话,呵呵,下午翻了翻,Tomcat5 的代码,只要option的getDeployment开关打开,也就是在开发模式下,他每次servlet的访问都去编译java文件,编译class文件,new新的ClassLoader,加载新的servlet。汗。。。神阿,你不要这样,怎么你也这么搞?搞得我以为你有什么绝招,翻了半天你的代码。----to Tomcat 如果不在开发模式下,JSP他不更新。但是有一点他应该是优化了,他记录下来了资源的路径,直接加载,省去了查找了开销,应该来说查找的开销会比读文件多数倍,特别是项目文件多的时候。我觉得,CommonTemplate有时间的话也可以这么做,在TemplateLock里面记录path信息,应该不叫TemplateLock,应该改成 ResorceEntry。 不知道weblogic的ChangeWareClassLoader是不是也是这样? |
|
返回顶楼 | |
发表时间:2007-11-28
再重构:
private final ReloadController reloadController; private final TemplateSourceComparator templateSourceComparator; private final Cache cache; private static final class Entry { Entry() {} private Object resource = null; Object get() { return resource; } void set(Object template) { this.resource = template; } } private Template cache(String name, String encoding) throws IOException, ParseException { Entry entry = null; // 先进行一次无锁读取,此过程不调用任何有副作用(修改状态)的函数,也不调用任何耗时操作 if (reloadController == null || ! reloadController.shouldReload(name)) { // 不需要热加载时才进行 entry = (Entry)cache.get(name); if (entry != null) { Template template = (Template)entry.get(); if (template != null) return template; } } // 缓存总锁,锁定各条目的获取 synchronized(cache){ entry = (Entry)cache.get(name); // 这里必需重新读取 if(entry == null){ entry = new Entry(); cache.put(name, entry); } assert(entry != null); // 后验条件 } // 单条目锁,锁定资源获取过程 synchronized (entry) { Template template = (Template)entry.get(); if (template == null) { // 不存在,解析加载 TemplateSource templateSource = load(name, encoding); template = parse(templateSource); entry.set(template); } else { // 已存在,检查热加载 if (reloadController != null && reloadController.shouldReload(name)) { // 是否需要检查热加载 TemplateSource templateSource = load(name, encoding); if (templateSourceComparator != null && templateSourceComparator.isModified(template, templateSource)) { // 比较是否已更改 template = parse(templateSource); entry.set(template); return template; } } } assert(template != null); // 后验条件 return template; } } |
|
返回顶楼 | |