如果接触过多线程编程或者大规模并发应用的开发的人都应该知道Readers-writer lock的设计模式,从英文字面上看就是对于资源允许多个Reader(复数)并发读,单个Writer写的锁机制,而Reader和Writer互斥。
现在的JDK里有一个ReadWriteLock的接口和一个ReentrantReadWriteLock的实现类,而其作者是赫赫有名的Doug Lea大牛(他有本 Concurrent Programming in Java Design Principles and Pattern ,推荐一下) 。早在JDK 1.4的时代,他就发表了自己的cocurrent包实现,其中就有多个ReadWriteLock的实现。下面会先聊一下早期Doug Lea在EDU.oswego.cs.dl.util.concurrent版本中对ReadWriteLock的实现,***再说JDK版本的。
1.EDU.oswego.cs.dl.util.concurrent的实现
Doug Lea的这个版本:EDU.oswego.cs.dl.util.concurrent包中ReadWriteLock所包含的内容比JDK要丰富不少,除了ReentrantReadWriteLock还有若干个其他实现。先看一下ReadWriteLock在其中的类继承关系。源代码下载
这其中包含了4个ReadWriteLock,包括先进先出的FIFOReadWriteLock ,Writer优先的WriterPreferenceReadWriteLock ,Reader优先的ReaderPreferenceReadWriteLock ,可重入ReentrantWriterPreferenceReadWriteLock 。
1.1 EDU.oswego.cs.dl.util.concurrent.ReadWriteLock接口
Java代码
- public interface ReadWriteLock {
- /** get the readLock **/
- Sync readLock();
- /** get the writeLock **/
- Sync writeLock();
- }
ReadWriteLock的接口定义和后来JDK的实现基本一致。readLock和writeLock都实现Sync接口,这个接口两个主要的方法是acquire和realse,在以后的JDK中,变成了Lock的lock方法和unlock方法。
1.2 WriterPreferenceReadWriteLock类
这个类包含了WriterLock类型的writerLock_和ReaderLock类型的readerLock_两个成员,而这两个类型又分别为WriterPreferenceReadWriteLock的内部类,这样做的一个考虑可能是为了能够让这两个类能够访问WriterPreferenceReadWriteLock的成员以及互相访问。
先看看WriterPreferenceReadWriteLock中ReaderLock的实现
该类的几个成员以及解释如下
Java代码
- /*用来给Writer判断是否可以拿到write控制权*/
- protected long activeReaders_ = 0;
- /*当前writer线程*/
- protected Thread activeWriter_ = null;
- /*可以用来作为策略调整,此版本中没有太多作用*/
- protected long waitingReaders_ = 0;
- /*等待中的写,用来给Reader判断是否还有Writer在等待,以便实现Writer优先*/
- protected long waitingWriters_ = 0;
- /*实际的ReaderLock*/
- protected final ReaderLock readerLock_ = new ReaderLock();
- /*实际的WriterLock*/
- protected final WriterLock writerLock_ = new WriterLock();
先来看看 ReaderLock,它的两个主要方法acquire和release
其中acquire的代码如下
Java代码
- public void acquire() throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- InterruptedException ie = null;
- synchronized (this) {
- /**
- * 判断是否能够获得读权
- */
- if (!startReadFromNewReader()) {
- for (;;) {
- try {
- /**
- * 等待notify
- */
- ReaderLock.this.wait();
- /**
- * 再次判断能否获得读权
- * 因为此处是Writer优先,当一个writer释放时,
- * reader还必须等待其他wait的writer获得控制权并释放后才能获得控制权
- */
- if (startReadFromWaitingReader())
- return;
- } catch (InterruptedException ex) {
- cancelledWaitingReader();
- ie = ex;
- break;
- }
- }
- }
- }
- if (ie != null) {
- // fall through outside synch on interrupt.
- // This notification is not really needed here,
- // but may be in plausible subclasses
- writerLock_.signalWaiters();
- throw ie;
- }
- }
acquire调用startReadFromNewReader,startReadFromWaitingReader,以及allowReader方法,这三个方法都属于WriterPreferenceReadWriteLock类
Java代码
- protected synchronized boolean startReadFromNewReader() {
- boolean pass = startRead();
- if (!pass)
- ++waitingReaders_;
- return pass;
- }
- protected synchronized boolean startReadFromWaitingReader() {
- boolean pass = startRead();
- if (pass)
- --waitingReaders_;
- return pass;
- }
- protected boolean allowReader() {
- //通过是否有正有控制权的activeWriter_和等待中的waitingWriters_来判断是Reader是否能获得控制权
- return activeWriter_ == null && waitingWriters_ == 0;
- }
- protected synchronized boolean startRead() {
- boolean allowRead = allowReader();
- if (allowRead)
- ++activeReaders_;
- return allowRead;
- }
另外release的代码如下
Java代码
- public void release() {
- Signaller s = endRead();
- if (s != null)
- s.signalWaiters();
- }
- protected synchronized Signaller endRead() {
- //只有当没有Reader在控制以及有等待的Writer的时候才返回
- //因为Reader之间没有互斥,所以返回writerLock
- if (--activeReaders_ == 0 && waitingWriters_ > 0)
- return writerLock_;
- else
- return null;
- }
这里要注意的是endRead返回的是writerLock,这样他就可以完成notify和它互斥的writer
下面看一下WriterLock的实现,同样也是acquire和release方法
先看acquire
Java代码
- public void acquire() throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- InterruptedException ie = null;
- synchronized (this) {
- //试图获得writer权
- if (!startWriteFromNewWriter()) {
- for (;;) {
- try {
- WriterLock.this.wait();
- /**
- * 重新判断是否能获得控制权
- * 这时如果是writerLock的notify的话,理论上只有一个reader或者writer能够结束等待
- * 如果是readerLock的notify的话,因为调用的是notifyAll,所以就必须重新竞争控制权
- */
- if (startWriteFromWaitingWriter())
- return;
- } catch (InterruptedException ex) {
- cancelledWaitingWriter();
- WriterLock.this.notify();
- ie = ex;
- break;
- }
- }
- }
- }
- if (ie != null) {
- // Fall through outside synch on interrupt.
- // On exception, we may need to signal readers.
- // It is not worth checking here whether it is strictly
- // necessary.
- readerLock_.signalWaiters();
- throw ie;
- }
- }
再看acquire调用的startWriteFromNewWriter,startWriteFromWaitingWriter和startWrite
Java代码
- protected synchronized boolean startWriteFromNewWriter() {
- boolean pass = startWrite();
- //如果不能获得控制权,则等待数+1
- if (!pass)
- ++waitingWriters_;
- return pass;
- }
- protected synchronized boolean startWrite() {
- // The allowWrite expression cannot be modified without
- // also changing startWrite, so is hard-wired
- //是否能获得写控制取决与是否已经有人有控制权(包括读和写)
- boolean allowWrite = (activeWriter_ == null && activeReaders_ == 0);
- if (allowWrite)
- activeWriter_ = Thread.currentThread();
- return allowWrite;
- }
- protected synchronized boolean startWriteFromWaitingWriter() {
- boolean pass = startWrite();
- //如果能获得控制权,则等待数-1
- if (pass)
- --waitingWriters_;
- return pass;
- }
再来看看WriterLock的release实现
Java代码
- public void release() {
- Signaller s = endWrite();
- if (s != null)
- //如果没有write的waiter,返回的的是readerLock_,则通知所有waiting的reader结束等待
- //如果有write的waiter,返回的的是writeLock,则通知一个正在等待的writer获得控制权
- s.signalWaiters();
- }
- protected synchronized Signaller endWrite() {
- activeWriter_ = null;
- //如果没有writer,则通知所有的等待的reader
- if (waitingReaders_ > 0 && allowReader())
- return readerLock_;
- //优先通知一个等待的writer
- else if (waitingWriters_ > 0)
- return writerLock_;
- else
- return null;
- }
***看看WriterLock和ReaderLock如何实现Writer优先
* 首先在竞争控制权时,Reader在竞争控制权必须确认既没有占用控制权的Writer也没有等待控制权的writer
这里是调用的是allowRead方法
activeWriter_ == null && waitingWriters_ == 0;
* 其次在WriterLock的release方法里,调用endWrite,然后也会调用allowReader,确认没有等待的Writer才会返回readerLock_,并在signalWaiters里调用notifyAll通知所有的等待reader结束等待。而一旦有writer等待,则调用writerLock_,只通知等待的writer竞争控制权。具体代码见上文。
* 同时在ReaderLock的release方法里,调用endRead,返回writerLock_,通知等待的writer竞争控制权。具体代码见上文。
到此为止WriterPreferenceReadWriteLock的实现基本说完,这个版本的实现下载见http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
#p#
1.3 EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock
上一篇说到了WriterPreferenceReadWriteLock,这一篇说一下ReentrantWriterPreferenceReadWriteLock。
顾名思义,该类实现的是可重入的的WriterPreferenceReadWriteLock。允许readers和writers在各自线程里反复获得读或写的锁。这种方法主要用在
该类是WriterPreferenceReadWriteLock的子类,与父类大体流程一致,但是startWrite,startRead ,allowReader ,endRead和endWrite被重写,下面逐一解释。
先看看新添加的成员变量
Java代码
- /**在activeWriter线程里,有多少次write锁的控制权被获取**/
- protected long writeHolds_ = 0;
- /**存放的每个reader线程共申请获得多少次读控制,key值是thread对象,value是次数**/
- protected HashMap readers_ = new HashMap();
- /*一个int的常量缓存*/
- protected static final Integer IONE = new Integer(1);
再来看看Reader部分的重写函数
Java代码
- protected boolean allowReader() {
- //与父类的变化在于,activeWriter_ == Thread.currentThread(),
- //也就是说当本线程已经获得写控制的时候,返回true
- return (activeWriter_ == null && waitingWriters_ == 0) ||
- activeWriter_ == Thread.currentThread();
- }
- protected synchronized boolean startRead() {
- Thread t = Thread.currentThread();
- //查看本线程是否已经获得读控制
- Object c = readers_.get(t);
- if (c != null) { // already held -- just increment hold count
- //计数+1
- readers_.put(t, new Integer(((Integer)(c)).intValue()+1));
- ++activeReaders_;
- return true;
- }
- //调用allowReader
- else if (allowReader()) {
- //将本线程获锁次数纪录在readers_这个map里
- readers_.put(t, IONE);
- ++activeReaders_;
- return true;
- }
- else
- return false;
- }
- protected synchronized Signaller endRead() {
- Thread t = Thread.currentThread();
- Object c = readers_.get(t);
- if (c == null)
- throw new IllegalStateException();
- --activeReaders_;
- if (c != IONE) { // 多于一个读线程有控制权,more than one hold; 计数递减
- int h = ((Integer)(c)).intValue()-1;
- Integer ih = (h == 1)? IONE : new Integer(h);
- readers_.put(t, ih);
- return null;
- }
- else {
- readers_.remove(t);
- if (writeHolds_ > 0) // 本线程还有写锁在占用控制权
- return null;
- //其余与父类实现一样
- else if (activeReaders_ == 0 && waitingWriters_ > 0)
- return writerLock_;
- else
- return null;
- }
- }
Reader部分相对于父类的变化在于
* 每个reader试图竞争控制权时,都会将本线程句柄与activeWriter进行比较,相同则认为是可以重入。
* 每个reader线程都在readers_的map里有一个计数器,判断当前有多少次获得reader锁权,释放的时候,只有当计数器为0时才通知其他写线程结束wait。
接着看看Writer部分的重写函数
Java代码
- protected synchronized boolean startWrite() {
- if (activeWriter_ == Thread.currentThread()) { // 反复重入
- ++writeHolds_;
- return true;
- }
- else if (writeHolds_ == 0) {
- //如果没有人在读(activeReaders_==0)和,或者在读的线程和本线程一样,并且readers里没有其他线程
- if (activeReaders_ == 0 ||
- (readers_.size() == 1 &&
- readers_.get(Thread.currentThread()) != null)) {
- //如果本线程在读中,则也可以进入writeLock
- activeWriter_ = Thread.currentThread();
- writeHolds_ = 1;
- return true;
- }
- else
- return false;
- }
- else
- return false;
- }
- protected synchronized Signaller endWrite() {
- --writeHolds_;
- if (writeHolds_ > 0)
- //这是主要与父类不一样的地方,写锁计数标示仍然有写锁没有被释放
- return null;
- else {
- //与父类一致
- activeWriter_ = null;
- if (waitingReaders_ > 0 && allowReader())
- return readerLock_;
- else if (waitingWriters_ > 0)
- return writerLock_;
- else
- return null;
- }
- }
Writer主要与父类不同的地方是
* 通过writeHolds来记录本线程获得锁的次数,以便在release时正确调用通知机制
* 通过当前线程和activeWriter线程比较来实现重入
1.3 EDU.oswego.cs.dl.util.concurrent.ReaderPreferenceReadWriteLock
ReaderPreferenceReadWriteLock类和父类唯一不同就是allowReader方法
Java代码
- protected boolean allowReader() {
- //只要没有写,就允许读
- return activeWriter_ == null;
- }
这个类在大多数场景下都不太实用,多数人主要还是使用writer优先锁。
【编辑推荐】