读写锁,顾名思义。这是一种对于两种不同行为的同步器工具,而读写是两种互斥的行为(写的时候,不能读。读的时候不能写),因而ReentrantReadWriteLock锁也具备此个性。
1 用写公司简章过程了解读写锁
设想一下,你正在写一份公司外部治理的简章,而笔只有一支(写锁),所以只有当你拿到这只笔(获取写锁)后才能够写内容。写完后(开释锁),简章就能够张贴进来让共事去浏览了(同时浏览:读锁)。然而这个时候你发现简章上写的有些问题,须要从新批改一下。不过你须要等你这些共事看齐全部来到之后(开释读锁),你才能够拿着笔(写锁)去批改简章。批改实现后,为了保障这次写的没有问题,你决定本人先检查一下(持有写锁,再持有读锁),OK后你放下笔(开释写锁,这个过程就交锁降级:写锁->读锁),而后又把它张贴到布告栏中,让共事们去观看。
2
源码解析
这里只有来学习其设计,对于偏心锁非偏心锁,以及可重入的概念不再赘述,不太分明的可参考AQS-用配钥匙和保险箱了解可重入锁(ReentrantLock)这篇文章的解析。读写锁也是通过继承AQS同步器来实现其性能的,它同时应用了排他锁(写锁)和共享锁(读锁),当初看它是怎么实现将读锁和写锁关联起来的。
构造函数:
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); // sync = lock.sync; writerLock = new WriteLock(this); // sync = lock.sync; }
读写锁,最终应用的都是这里结构的sync对象,依据fair参数生成偏心锁或非偏心锁。接下来咱们来看一下读锁的获取/开释,以及写锁的获取/开释。
// 读锁 public void lock() { sync.acquireShared(1); } public void unlock() { sync.releaseShared(1); }// 写锁 public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); }
能够看到的是,读锁应用的是共享锁逻辑,而写锁用的是排他锁的逻辑,同时要留神其可重入的。这些都很简略的,咱们后面的文章都有讲述到。这里的难点是读锁和写锁到底是这么关联的?解析源码前,咱们先来看一下Sync这个class的成员信息,咱们就能明确了ReentrantReadWriteLock是怎么实现的了:
static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 读锁最大数量 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; /** 借出共享锁数量 */ static int sharedCount(int c) { return c >>> SHARED_SHIFT; } /** 写锁的数量 */ static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
设计者将int32分为两局部:前16位(存储读锁信息),后16位(存储写锁信息)。咱们再依据其提供的sharedCount,exclusiveCount 办法名就能够明确,ReentrantReadWriteLock读写锁就是获取读锁数量和写锁数量来实现关联,从而实现互斥性能。当初咱们来看一下理论代码:
读锁:
// 读锁lock sync.acquireShared(1)最终调用tryReleaseShared protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && ## 1 getExclusiveOwnerThread() != current) // 判断是否能够锁降级(持有写锁的,获取读锁) return -1; // 失败。因为曾经有人持有写锁了 int r = sharedCount(c); // 读锁的数量 if (!readerShouldBlock() && ## 2 r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { ## 3 firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } return fullTryAcquireShared(current); // 完整版,用了for循环,代码相似不赘述。 }
阐明下代码:
1:if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) 代码实现的是读写锁的互斥以及写锁的降级。解释一下:写锁数量不为0,以后线程不是索取写锁的线程。只有if为false能力获取读锁,有以下两种情状况:a以后没有线程写锁; b.是以后线程获取写锁。
2 : 这里的逻辑就是获取读锁,应用了CAS。
3 : 这里阐明一下 :firstReader和cachedHoldCounter记录第一个和最初一个线程是为了优化获取锁的效率。firstReader(只有一个线程时反复获取锁)。cachedHoldCounter(最初一个获取锁的线程反复获取锁)。因为这两种状况最有可能产生。
写锁:
// 写锁lock sync.acquireShared(1)最终调用tryReleaseShared protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); // 状态值 int w = exclusiveCount(c); // 写锁数量 if (c != 0) { ## 1 // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) // 最大重入数量 throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || // 获取读锁锁逻辑 !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); // 设置以后线程,便于重入 return true; }
1: 状态值state不为0,意味着有线程持有锁(写锁,读锁)。 if (w == 0 || current != getExclusiveOwnerThread()) ,写锁数量为0,那么当初还有读锁未开释,获取失败。如果写锁数量不为0,但不是以后线程则不可重入。
对于读写锁的开释比较简单,这里就不再解析了。同时须要说一声的是对于java AQS这块源码学习就先到这了。