多线程读取并修必一个资源时,我们过去通常使用synchronized同步锁,这个是有性能损失的,很多情况下:资源对象总是被大量并发读取,偶尔有一个线程进行修改,也就是说:以读为主,修改不是很频繁,那么我们在JDK5中用ReentrantReadWriteLock就获得比synchronized更高并发性能,高并发性能是我们使用JDK5的主要目的,而不是annotaion和泛型等设计优点。
在使用ReentrantReadWriteLock 时应明确以下几点:
(1)在代码中添加读锁(lock.readLock().lock() ) 的目的:如果有一线程在读取数据时,此时恰好有另一线程,正以写锁的方式在修改该数据,此时读取数据的线程如果加了读锁,将被阻塞,直到另一线程的写锁被释放才能继续读,而如果读取数据的线程未加读锁,那么它的读将不被阻塞,就有可能读入被修改前的数据。总结:读锁离开了写锁就没有了意义
(2)重入:此锁允许 reader 和 writer 按照 ReentrantLoc 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入 reader 使用它们。此外,writer 可以获取读取锁,但反过来则不成立。在其他应用程序中,当在调用或回调那些在读取锁状态下执行读取操作的方法期间保持写入锁时,重入很有用。如果 reader 试图获取写入锁,那么将永远不会获得成功。reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized
的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized
块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized
块时,才释放锁。这相当于是模仿了synchronized中又可以嵌套一个synchronized这样的场景
(3)锁降级:重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。
下面是JDK的一个例子,展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理):
class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); // Recheck state because another thread might have acquired // write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); rwl.writeLock().unlock(); // Unlock write, still hold read } use(data); rwl.readLock().unlock(); } }