说到ReentrantReadWriteLock,首先要作的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。 html
ReentrantLock 实现了标准的互斥操做,也就是一次只能有一个线程持有锁,也即所谓独占锁的概念。前面的章节中一直在强调这个特色。显然这个特色在必定程度上面减低了吞吐量,实际上独占锁是一种保守的锁策略,在这种状况下任何“读/读”,“写/读”,“写/写”操做都不能同时发生。可是一样须要强调的一个概念是,锁是有必定的开销的,当并发比较大的时候,锁的开销就比较客观了。因此若是可能的话就尽可能少用锁,非要用锁的话就尝试看可否改造为读写锁。 java
ReadWriteLock 描述的是:一个资源可以被多个读线程访问,或者被一个写线程访问,可是不能同时存在读写线程。也就是说读写锁使用的场合是一个共享资源被大量读取操做,而只有少许的写操做(修改数据)。清单0描述了ReadWriteLock的API。 缓存
清单0 ReadWriteLock 接口 并发
- public interface ReadWriteLock {
- Lock readLock();
- Lock writeLock();
- }
清单0描述的ReadWriteLock结构,这里须要说明的是ReadWriteLock并非Lock的子接口,只不过ReadWriteLock借助Lock来实现读写两个视角。在ReadWriteLock中每次读取共享数据就须要读取锁,当须要修改共享数据时就须要写入锁。看起来好像是两个锁,但其实不尽然,下文会指出。 app
ReentrantReadWriteLock有如下几个特性: 性能
ReentrantReadWriteLock里面的锁主体就是一个Sync,也就是上面提到的FairSync或者NonfairSync,因此说实际上只有一个锁,只是在获取读取锁和写入锁的方式上不同,因此前面才有读写锁是独占锁的两个不一样视图一说。 ui
ReentrantReadWriteLock里面有两个类:ReadLock/WriteLock,这两个类都是Lock的实现。 spa
清单1 ReadLock 片断 .net
- public static class ReadLock implements Lock, java.io.Serializable {
- private final Sync sync;
- protected ReadLock(ReentrantReadWriteLock lock) {
- sync = lock.sync;
- }
- public void lock() {
- sync.acquireShared(1);
- }
- public void lockInterruptibly() throws InterruptedException {
- sync.acquireSharedInterruptibly(1);
- }
- public boolean tryLock() {
- return sync.tryReadLock();
- }
- public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
- return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
- }
- public void unlock() {
- sync.releaseShared(1);
- }
- public Condition newCondition() {
- throw new UnsupportedOperationException();
- }
- }
清单2 WriteLock 片断 线程
- public static class WriteLock implements Lock, java.io.Serializable {
- private final Sync sync;
- protected WriteLock(ReentrantReadWriteLock lock) {
- sync = lock.sync;
- }
- public void lock() {
- sync.acquire(1);
- }
- public void lockInterruptibly() throws InterruptedException {
- sync.acquireInterruptibly(1);
- }
- public boolean tryLock( ) {
- return sync.tryWriteLock();
- }
- public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
- return sync.tryAcquireNanos(1, unit.toNanos(timeout));
- }
- public void unlock() {
- sync.release(1);
- }
- public Condition newCondition() {
- return sync.newCondition();
- }
- public boolean isHeldByCurrentThread() {
- return sync.isHeldExclusively();
- }
- public int getHoldCount() {
- return sync.getWriteHoldCount();
- }
- }
清单1描述的是读锁的实现,清单2描述的是写锁的实现。显然WriteLock就是一个独占锁,这和ReentrantLock里面的实现几乎相同,都是使用了AQS的acquire/release操做。固然了在内部处理方式上与ReentrantLock仍是有一点不一样的。对比清单1和清单2能够看到,ReadLock获取的是共享锁,WriteLock获取的是独占锁。
在AQS章节中介绍到AQS中有一个state字段(int类型,32位)用来描述有多少线程获持有锁。在独占锁的时代这个值一般是0或者1(若是是重入的就是重入的次数),在共享锁的时代就是持有锁的数量。在上一节中谈到,ReadWriteLock的读、写锁是相关可是又不一致的,因此须要两个数来描述读锁(共享锁)和写锁(独占锁)的数量。显然如今一个state就不够用了。因而在ReentrantReadWrilteLock里面将这个字段一分为二,高位16位表示共享锁的数量,低位16位表示独占锁的数量(或者重入数量)。2^16-1=65536,这就是上节中提到的为何共享锁和独占锁的数量最大只能是65535的缘由了。
有了上面的知识后再来分析读写锁的获取和释放就容易多了。
清单3 写入锁获取片断
- protected final boolean tryAcquire(int acquires) {
- Thread current = Thread.currentThread();
- int c = getState();
- int w = exclusiveCount(c);
- if (c != 0) {
- if (w == 0 || current != getExclusiveOwnerThread())
- return false;
- if (w + exclusiveCount(acquires) > MAX_COUNT)
- throw new Error("Maximum lock count exceeded");
- }
- if ((w == 0 && writerShouldBlock(current)) ||
- !compareAndSetState(c, c + acquires))
- return false;
- setExclusiveOwnerThread(current);
- return true;
- }
清单3 是写入锁获取的逻辑片断,整个工做流程是这样的:
清单3 中 exclusiveCount(c)就是获取写线程数(包括重入数),也就是state的低16位值。另外这里有一段逻辑是当前写线程是否须要阻塞writerShouldBlock(current)。清单4 和清单5 就是公平锁和非公平锁中是否须要阻塞的片断。很显然对于非公平锁而言老是不阻塞当前线程,而对于公平锁而言若是AQS队列不为空或者当前线程不是在AQS的队列头那么就阻塞线程,直到队列前面的线程处理完锁逻辑。
清单4 公平读写锁写线程是否阻塞
- final boolean writerShouldBlock(Thread current) {
- return !isFirst(current);
- }
清单5 非公平读写锁写线程是否阻塞
- final boolean writerShouldBlock(Thread current) {
- return false;
- }
写入锁的获取逻辑清楚后,释放锁就比较简单了。清单6 描述的写入锁释放逻辑片断,其实就是检测下剩下的写入锁数量,若是是0就将独占锁线程清空(意味着没有线程获取锁),不然就是说当前是重入锁的一次释放,因此不能将独占锁线程清空。而后将剩余线程状态数写回AQS。
清单6 写入锁释放逻辑片断
- protected final boolean tryRelease(int releases) {
- int nextc = getState() - releases;
- if (Thread.currentThread() != getExclusiveOwnerThread())
- throw new IllegalMonitorStateException();
- if (exclusiveCount(nextc) == 0) {
- setExclusiveOwnerThread(null);
- setState(nextc);
- return true;
- } else {
- setState(nextc);
- return false;
- }
- }
清单3~6 描述的写入锁的获取释放过程。读取锁的获取和释放过程要稍微复杂些。 清单7描述的是读取锁的获取过程。
清单7 读取锁获取过程片断
- protected final int tryAcquireShared(int unused) {
- Thread current = Thread.currentThread();
- int c = getState();
- if (exclusiveCount(c) != 0 &&
- getExclusiveOwnerThread() != current)
- return -1;
- if (sharedCount(c) == MAX_COUNT)
- throw new Error("Maximum lock count exceeded");
- if (!readerShouldBlock(current) &&
- compareAndSetState(c, c + SHARED_UNIT)) {
- HoldCounter rh = cachedHoldCounter;
- if (rh == null || rh.tid != current.getId())
- cachedHoldCounter = rh = readHolds.get();
- rh.count++;
- return 1;
- }
- return fullTryAcquireShared(current);
- }
- final int fullTryAcquireShared(Thread current) {
- HoldCounter rh = cachedHoldCounter;
- if (rh == null || rh.tid != current.getId())
- rh = readHolds.get();
- for (;;) {
- int c = getState();
- int w = exclusiveCount(c);
- if ((w != 0 && getExclusiveOwnerThread() != current) ||
- ((rh.count | w) == 0 && readerShouldBlock(current)))
- return -1;
- if (sharedCount(c) == MAX_COUNT)
- throw new Error("Maximum lock count exceeded");
- if (compareAndSetState(c, c + SHARED_UNIT)) {
- cachedHoldCounter = rh; // cache for release
- rh.count++;
- return 1;
- }
- }
- }
读取锁获取的过程是这样的:
在清单7 中有一个对象HoldCounter,这里暂且不提这是什么结构和为何存在这样一个结构。
接下来根据清单8 咱们来看如何释放一个读取锁。一样先不理HoldCounter,关键的在于for循环里面,其实就是一个不断尝试的CAS操做,直到修改状态成功。前面说过state的高16位描述的共享锁(读取锁)的数量,因此每次都须要减去2^16,这样就至关于读取锁数量减1。实际上SHARED_UNIT=1<<16。
清单8 读取锁释放过程
- protected final boolean tryReleaseShared(int unused) {
- HoldCounter rh = cachedHoldCounter;
- Thread current = Thread.currentThread();
- if (rh == null || rh.tid != current.getId())
- rh = readHolds.get();
- if (rh.tryDecrement() <= 0)
- throw new IllegalMonitorStateException();
- for (;;) {
- int c = getState();
- int nextc = c - SHARED_UNIT;
- if (compareAndSetState(c, nextc))
- return nextc == 0;
- }
- }
好了,如今回头看HoldCounter究竟是一个什么东西。首先咱们能够看到只有在获取共享锁(读取锁)的时候加1,也只有在释放共享锁的时候减1有做用,而且在释放锁的时候抛出了一个IllegalMonitorStateException异常。而咱们知道IllegalMonitorStateException一般描述的是一个线程操做一个不属于本身的监视器对象的引起的异常。也就是说这里的意思是一个线程释放了一个不属于本身或者不存在的共享锁。
前面的章节中一再强调,对于共享锁,其实并非锁的概念,更像是计数器的概念。一个共享锁就相对于一次计数器操做,一次获取共享锁至关于计数器加1,释放一个共享锁就至关于计数器减1。显然只有线程持有了共享锁(也就是当前线程携带一个计数器,描述本身持有多少个共享锁或者多重共享锁),才能释放一个共享锁。不然一个没有获取共享锁的线程调用一次释放操做就会致使读写锁的state(持有锁的线程数,包括重入数)错误。
明白了HoldCounter的做用后咱们就能够猜到它的做用其实就是当前线程持有共享锁(读取锁)的数量,包括重入的数量。那么这个数量就必须和线程绑定在一块儿。
在Java里面将一个对象和线程绑定在一块儿,就只有ThreadLocal才能实现了。因此毫无疑问HoldCounter就应该是绑定到线程上的一个计数器。
清单9 线程持有读取锁数量的计数器
- static final class HoldCounter {
- int count;
- final long tid = Thread.currentThread().getId();
- int tryDecrement() {
- int c = count;
- if (c > 0)
- count = c - 1;
- return c;
- }
- }
- static final class ThreadLocalHoldCounter
- extends ThreadLocal<HoldCounter> {
- public HoldCounter initialValue() {
- return new HoldCounter();
- }
- }
清单9 描述的是线程持有读取锁数量的计数器。能够看到这里使用ThreadLocal将HoldCounter绑定到当前线程上,同时HoldCounter也持有线程Id,这样在释放锁的时候才能知道ReadWriteLock里面缓存的上一个读取线程(cachedHoldCounter)是不是当前线程。这样作的好处是能够减小ThreadLocal.get()的次数,由于这也是一个耗时操做。须要说明的是这样HoldCounter绑定线程id而不绑定线程对象的缘由是避免HoldCounter和ThreadLocal互相绑定而GC难以释放它们(尽管GC可以智能的发现这种引用而回收它们,可是这须要必定的代价),因此其实这样作只是为了帮助GC快速回收对象而已。
除了readLock()和writeLock()外,Lock对象还容许tryLock(),那么ReadLock和WriteLock的tryLock()不同。清单10 和清单11 分别描述了读取锁的tryLock()和写入锁的tryLock()。
读取锁tryLock()也就是tryReadLock()成功的条件是:没有写入锁或者写入锁是当前线程,而且读线程共享锁数量没有超过65535个。
写入锁tryLock()也就是tryWriteLock()成功的条件是: 没有写入锁或者写入锁是当前线程,而且尝试一次修改state成功。
清单10 读取锁的tryLock()
- final boolean tryReadLock() {
- Thread current = Thread.currentThread();
- for (;;) {
- int c = getState();
- if (exclusiveCount(c) != 0 &&
- getExclusiveOwnerThread() != current)
- return false;
- if (sharedCount(c) == MAX_COUNT)
- throw new Error("Maximum lock count exceeded");
- if (compareAndSetState(c, c + SHARED_UNIT)) {
- HoldCounter rh = cachedHoldCounter;
- if (rh == null || rh.tid != current.getId())
- cachedHoldCounter = rh = readHolds.get();
- rh.count++;
- return true;
- }
- }
- }
清单11 写入锁的tryLock()
- final boolean tryWriteLock() {
- Thread current = Thread.currentThread();
- int c = getState();
- if (c != 0) {
- int w = exclusiveCount(c);
- if (w == 0 ||current != getExclusiveOwnerThread())
- return false;
- if (w == MAX_COUNT)
- throw new Error("Maximum lock count exceeded");
- }
- if (!compareAndSetState(c, c + 1))
- return false;
- setExclusiveOwnerThread(current);
- return true;
- }
4、小结
使用ReentrantReadWriteLock能够推广到大部分读,少许写的场景,由于读线程之间没有竞争,因此比起sychronzied,性能好不少。
若是须要较为精确的控制缓存,使用ReentrantReadWriteLock倒也不失为一个方案。
参考内容来源:
ReentrantReadWriteLock http://uule.iteye.com/blog/1549707
深刻浅出 Java Concurrency (13): 锁机制 part 8 读写锁 (ReentrantReadWriteLock) (1)
http://www.blogjava.net/xylz/archive/2010/07/14/326080.html
深刻浅出 Java Concurrency (14): 锁机制 part 9 读写锁 (ReentrantReadWriteLock) (2)
http://www.blogjava.net/xylz/archive/2010/07/15/326152.html
高性能锁ReentrantReadWriteLock
http://jhaij.iteye.com/blog/269656
JDK说明
http://www.cjsdn.net/Doc/JDK60/java/util/concurrent/locks/ReentrantReadWriteLock.html
关于concurrent包 线程池、资源封锁和队列、ReentrantReadWriteLock介绍
http://www.oschina.net/question/16_636