高性能锁ReentrantReadWriteLock

08-10-17 banq
多线程读取并修改一个资源时,我们过去通常使用synchronized同步锁,这个是有性能损失的,很多情况下:资源对象总是被大量并发读取,偶尔有一个线程进行修改,也就是说:以读为主,修改不是很频繁,那么我们在JDK5.0中用ReentrantReadWriteLock就获得比synchronized更高并发性能,高并发性能是我使用JDK5.0主要目的,而不是annotation和泛型等设计优点。

ReentrantReadWriteLock被大量使用在缓存中,因为缓存中的对象总是被共享大量读操作,偶尔修改这个对象中的子对象,比如状态,那么只要通过ReentrantReadWriteLock来更新子对象就可以了,这就实现了Evans DDD中对不变性的要求,我们可以使用ReentrantReadWriteLock对根对象中生命周期短的子对象在内存中直接更新,不必依赖数据库锁,这又是一个摆脱数据库锁的进步。

以JiveJdon为例子,ForumThread 通常情况下保存在缓存中,而且是一个单例,那么并发多次请求访问缓存中ForumThread。

ForumThread中有ForumThreadState,根据Evans DDD将经常变换的对象生命周期和其他不变量区分开来,这样,需要修改时不必锁住整个根对象,在DDD一书中,锁住整个子对象是通过数据库锁来实现,而JiveJdon中,我们的对象在缓存中,也必须有一个机制来实现这种细腻度锁。见“实战DDD”

http://www.jdon.com/mda/ddd.html

这个状态子对象是每发一个贴就要更新修改的,而平时ForumThreadState 和ForumThread大量被读取,只有回帖者一个请求进行修改,为使其他人立即看到帖子更新,原先需要使用synchronized,但性能影响,现在就可以使用ReentrantReadWriteLock

protected final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
protected final Lock read = readWriteLock.readLock();
protected final Lock write = readWriteLock.writeLock();

public ForumThreadState getState() {
    	read.lock();
		try {
			return state;
		} finally {
			read.unlock();
		}
    }
    /**
     * @param forumThreadState The forumThreadState to set.
     */
    public void setState(ForumThreadState forumThreadState) {
    	write.lock();
    	try {
    		this.state = forumThreadState;
		} finally {
			write.unlock();
		}
        
    }
<p>

为什么要用synchronized?不用synchronized你的对象中数据被修改后,无法使其他线程更新。

http://www.ibm.com/developerworks/java/library/j-praxis/pr50.html

ReentrantReadWriteLock案例

http://ilkinbalkanay.blogspot.com/2008/01/readwritelock-example-in-java.html

ReentrantReadWriteLock原理

http://java.dzone.com/news/java-concurrency-read-write-lo?page=0,0

[该贴被banq于2008-10-17 22:36修改过]

                   

5
oojdon
2008-10-17 23:38
好,现在可以把原来的状态对象抓出来加锁更新了,不用读取数据库,是吗?

freeren
2008-10-18 09:46
哈哈,学习了,一会去试一下,谢谢老师的分享!

banq
2008-10-27 13:44
推荐文章:

Software Transactional Memory in Scala

在多用户对内存疯狂写的情况下,scala语言有独特解决方式。

http://www.codecommit.com/blog/scala/software-transactional-memory-in-scala

关于Java线程并发可以参考“JAVA并发编程实践”一书有中文译本,Java Concurrency in Practice(2006年出版),该书对JDK5.0的各种锁进行了介绍,包括ReentrantReadWriteLock ReentrantLock 和Synchronized 对比使用,ReentrantReadWriteLock 的使用还是有相当前提的。

由于Servlet/Web/JEE本质是一个多线程,特别是我们围绕对象编程,也就是OO编程,因为使用OO必然带来性能慢,有人说:Java慢不是语言慢,而是因为OO,因此,为使OO更实用,必须结合对象缓存Object Cache,当OO+缓存时,涉及缓存中的对象更新问题,如果这个对象又被其他对象作为子对象关联,如何保证它们的生命周期一致,等等课题都是现实中难点,缓存中对象更新就涉及多线程下的线程安全。

所以ReentrantReadWriteLock 等多线程所往往搞缓存产品研究的人研究多,是一个复杂课题。

JavaEE缓存中状态共享和大部分线程书籍中讲的多线程状态共享还有一点不同是:JavaEE多请求(实质是多线程)访问缓存中一个单例状态时,特别是进行状态更新时,必须保证原子操作,也就是所谓事务完整性,这时需要加锁,加锁技巧很重要,我不推荐不能按通常介绍加到操作方法上,因此操作方法是操作的是缓存中同一个类的不同单例实例,这和通常介绍演示的同一个类的就一个单例实例有区别的,一旦上锁,这样就很容易将多用户JavaEE变成单用户,性能很低,或者死锁。

所以,实战经验是:缺省情况下不用考虑锁,性能并发测试在最容易出现数据不一致问题的地方加锁,比如JiveJdon3开始就没有考虑锁,但是,每个主题贴发完贴后,发现这个主题贴的状态有时不能及时更新,这就是多线程的可视问题,所以,才采取楼上的措施,结合Volatile重构JiveJdon3。

但是还不够,发现在更新ForumThread的ForumThreadState状态时,逻辑有些问题,这就和多线程锁没有关系,原来更新状态是依靠ForumBuilder一个类来完成,其中根据Evans DDD的工厂创建对象理论封装Forum ForumThread Message等三个对象的创建,非常复杂,容易有逻辑漏洞,因此使用Builder设计模式重构,重构就是将原来粒度进一步减小,在分离解剖过程中,很容易发现逻辑漏洞,找到状态不能及时更新的业务逻辑漏洞,更新Thread状态TreeModel的步骤和Message判断是否是树叶是相互依赖的,必须保证前后执行顺序,因此通过Builder模式来固定这种执行顺序,

从这里看,因为OO是打乱了面向过程的执行顺序,但是如果你要强调保证执行顺序,可以使用Builder模式专门讲执行组装过程封装起来,达到总是封装保证目地。

所以,从以上看出:Evans DDD 设计模式 缓存 多线程安全 不变性和可变性 锁等概念都是在JavaEE中融合,这几者融合在一起才能共同组成一个设计有好,运行性能又快的系统。

如果你是基于数据库编程,那么就不会用到缓存和锁,因为你的锁都是使用数据库概念,只有数据被对象封装,并保存在缓存中,共享,才会涉及到锁 线程安全。

所以,Java多线程看似基础,实则高深高级。

[该贴被banq于2008-10-29 11:56修改过]

banq
2008-10-29 12:16
最近“JAVA并发编程实践”一书译者在infoQ又翻译了一篇“Java 6中的线程优化真的有效么?

”,从“JAVA并发编程实践”一书样章看,翻译得还是不错,用词各方面比较贴切。

http://www.infoq.com/cn/articles/java-threading-optimizations-p1

Java支持的锁模型绝对是悲观锁(其实,大多数线程库都是如此),而数据库提供乐观锁,是不是就意味着数据库的所比Java好呢?未必:

我们只要根据Evans DDD,划分好对象生命周期,将可变性和不变性进行分离,就可以使用悲观锁粒度影响降低,而且从业务逻辑上说,我们确实需要严格的悲观锁。

当然,如果你使用数据库乐观锁,还需要软件的版本校验功能,软件配合来实现,这是一个通用解决方案,没有我们根据业务具体定制锁的使用更具有针对性,就是精确制导。

java 6中Biased locking 偏向锁,就是为一个线程反复获得同一个锁时,降低性能开销;Lock coarsening 线程粗化违反锁粒度细的性能设计原则,意义不大,不如手工设计粒度;

从这篇文章看来,在JDK1.5以上环境,使用使用没有任何优化的同步的StringBuffer,其运行效率比StringBuilder大概要慢三倍。所以,字符可变推荐使用StringBuilder,这也算是一个小的性能优化技巧。

该文结论是:使用JDK1.6的锁优化如果可能和其他策略矛盾,其性能优化点可能被抑制。感觉这种锁研究就象象研究细胞微生物相互作用一样。

猜你喜欢
2Go 1 2 下一页