论坛首页 Java企业应用论坛

缓存同步策略重构

浏览 11010 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-11-24  
我想错了
0 请登录后投票
   发表时间:2007-11-24  
Quake Wang 写道
javatar 写道

Quake Wang 写道

4. 若已更改,清除缓存中对于的这个Key,并且跳到第一步
5. 若不存在,重新解析模板并将模板压入缓存

第5步会不会出现并发修改异常?

这依赖于Cache的实现,成熟的Cache解决方案都会在put方法内部解决并发问题。

正解。内存缓存,用一个同步的HashMap就可以了,譬如继承LinkedHashMap,重写方法removeEldestEntry便可以实现一个LRU策略的Map,够成熟。
0 请登录后投票
   发表时间:2007-11-24  
nihongye 写道

思路挺好,代码有问题。
syn..(globalLock)
syn..(resourceLock ){
之间未同步。不用同步块的形式



恩,只是我想:两个块之间还需要同步吗?

第一个同步块确保取到资源对应的唯一锁。
第二个同步块确保同一的资源不产生多个实例。

再第二个同步块里还有一次读取操作,我还没想到需要在两块之间同步的理由。
0 请登录后投票
   发表时间:2007-11-24  
jindw 写道
nihongye 写道

思路挺好,代码有问题。
syn..(globalLock)
syn..(resourceLock ){
之间未同步。不用同步块的形式



恩,只是我想:两个块之间还需要同步吗?

第一个同步块确保取到资源对应的唯一锁。
第二个同步块确保同一的资源不产生多个实例。

再第二个同步块里还有一次读取操作,我还没想到需要在两块之间同步的理由。

不用,是我搞错了:)
0 请登录后投票
   发表时间: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;
	}
}
0 请登录后投票
   发表时间: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) {   
        // 同上帖...
    }   
}

还有没有更好的方式?
如果没问题,准备下一个版本加入。
0 请登录后投票
   发表时间:2007-11-27  
这样不行,锁得的就是Load方法,还放在外面无锁预读,不是白锁了吗?(见上第9行),虽然说,两个线程同时Load没问题,只要Put的时候同步一下get就可以了,但是,大并发下,多个线程,同时Load一个资源,做同样的比较,不是太浪费了吗?这样锁得目的,两个:1)是保证线程安全,因为无法知道parse语法分析时会不会涉及一些共享资源
2)保证需要加载资源时,其他访问线程可以先等着,不要也去加载,浪费CPU和IO操作,load是非常慢的一个操作。


而且上面还有一个问题,就是热加载。每次访问都去load一下是不行的,load是一个秒级操作,对于系统级的组件来说(比如common template or JSP)这是绝对不能接受的。



0 请登录后投票
   发表时间: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操作。

0 请登录后投票
   发表时间: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是不是也是这样?


0 请登录后投票
   发表时间: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;
	}
}
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics