Java的ReadWriteLock实现机制解析

开发 后端
如果接触过多线程编程或者大规模并发应用的开发的人都应该知道Readers-writer lock的设计模式,从英文字面上看就是对于资源允许多个Reader(复数)并发读,单个Writer写的锁机制,而Reader和Writer互斥。

如果接触过多线程编程或者大规模并发应用的开发的人都应该知道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代码

 

 

  1. public interface ReadWriteLock {     
  2.   /** get the readLock **/    
  3.   Sync readLock();     
  4.     
  5.   /** get the writeLock **/    
  6.   Sync writeLock();     
  7. }    

 

ReadWriteLock的接口定义和后来JDK的实现基本一致。readLock和writeLock都实现Sync接口,这个接口两个主要的方法是acquire和realse,在以后的JDK中,变成了Lock的lock方法和unlock方法。

1.2 WriterPreferenceReadWriteLock类

这个类包含了WriterLock类型的writerLock_和ReaderLock类型的readerLock_两个成员,而这两个类型又分别为WriterPreferenceReadWriteLock的内部类,这样做的一个考虑可能是为了能够让这两个类能够访问WriterPreferenceReadWriteLock的成员以及互相访问。

先看看WriterPreferenceReadWriteLock中ReaderLock的实现

该类的几个成员以及解释如下

Java代码

 

 

  1. /*用来给Writer判断是否可以拿到write控制权*/    
  2. protected long activeReaders_ = 0;     
  3. /*当前writer线程*/    
  4. protected Thread activeWriter_ = null;     
  5. /*可以用来作为策略调整,此版本中没有太多作用*/    
  6. protected long waitingReaders_ = 0;     
  7. /*等待中的写,用来给Reader判断是否还有Writer在等待,以便实现Writer优先*/    
  8. protected long waitingWriters_ = 0;     
  9.     
  10. /*实际的ReaderLock*/    
  11. protected final ReaderLock readerLock_ = new ReaderLock();     
  12. /*实际的WriterLock*/    
  13. protected final WriterLock writerLock_ = new WriterLock();    

 

先来看看 ReaderLock,它的两个主要方法acquire和release

其中acquire的代码如下

Java代码

 

 

  1. public void acquire() throws InterruptedException {     
  2.     if (Thread.interrupted())     
  3.         throw new InterruptedException();     
  4.     InterruptedException ie = null;     
  5.     synchronized (this) {     
  6.         /**    
  7.          * 判断是否能够获得读权    
  8.          */    
  9.         if (!startReadFromNewReader()) {     
  10.             for (;;) {     
  11.                 try {     
  12.                     /**    
  13.                      * 等待notify    
  14.                      */    
  15.                     ReaderLock.this.wait();     
  16.                     /**    
  17.                      * 再次判断能否获得读权    
  18.                      * 因为此处是Writer优先,当一个writer释放时,    
  19.                      * reader还必须等待其他wait的writer获得控制权并释放后才能获得控制权    
  20.                      */    
  21.                     if (startReadFromWaitingReader())     
  22.                         return;     
  23.                 } catch (InterruptedException ex) {     
  24.                     cancelledWaitingReader();     
  25.                     ie = ex;     
  26.                     break;     
  27.                 }     
  28.             }     
  29.         }     
  30.     }     
  31.     if (ie != null) {     
  32.         // fall through outside synch on interrupt.     
  33.         // This notification is not really needed here,     
  34.         // but may be in plausible subclasses     
  35.         writerLock_.signalWaiters();     
  36.         throw ie;     
  37.     }     
  38. }   

 

acquire调用startReadFromNewReader,startReadFromWaitingReader,以及allowReader方法,这三个方法都属于WriterPreferenceReadWriteLock类

Java代码

 

 

  1. protected synchronized boolean startReadFromNewReader() {     
  2.     boolean pass = startRead();     
  3.     if (!pass)     
  4.         ++waitingReaders_;     
  5.     return pass;     
  6. }     
  7.     
  8.     
  9. protected synchronized boolean startReadFromWaitingReader() {     
  10.     boolean pass = startRead();     
  11.     if (pass)     
  12.         --waitingReaders_;     
  13.     return pass;     
  14. }     
  15.     
  16.     
  17.     
  18. protected boolean allowReader() {     
  19.     //通过是否有正有控制权的activeWriter_和等待中的waitingWriters_来判断是Reader是否能获得控制权     
  20.     return activeWriter_ == null && waitingWriters_ == 0;     
  21. }     
  22.     
  23.     
  24. protected synchronized boolean startRead() {     
  25.     boolean allowRead = allowReader();     
  26.     if (allowRead)     
  27.         ++activeReaders_;     
  28.     return allowRead;     
  29. }    

 

另外release的代码如下

Java代码

 

 

  1. public void release() {     
  2.     Signaller s = endRead();     
  3.     if (s != null)     
  4.         s.signalWaiters();     
  5. }     
  6.     
  7.     
  8. protected synchronized Signaller endRead() {     
  9.     //只有当没有Reader在控制以及有等待的Writer的时候才返回     
  10.     //因为Reader之间没有互斥,所以返回writerLock     
  11.     if (--activeReaders_ == 0 && waitingWriters_ > 0)     
  12.         return writerLock_;     
  13.     else    
  14.         return null;     
  15. }    

 

这里要注意的是endRead返回的是writerLock,这样他就可以完成notify和它互斥的writer

下面看一下WriterLock的实现,同样也是acquire和release方法

先看acquire

Java代码

 

 

  1. public void acquire() throws InterruptedException {     
  2.     if (Thread.interrupted())     
  3.         throw new InterruptedException();     
  4.     InterruptedException ie = null;     
  5.     synchronized (this) {     
  6.         //试图获得writer权     
  7.         if (!startWriteFromNewWriter()) {     
  8.             for (;;) {     
  9.                 try {     
  10.     
  11.                     WriterLock.this.wait();      
  12.                     /**    
  13.                      * 重新判断是否能获得控制权    
  14.                      * 这时如果是writerLock的notify的话,理论上只有一个reader或者writer能够结束等待    
  15.                      * 如果是readerLock的notify的话,因为调用的是notifyAll,所以就必须重新竞争控制权    
  16.                      */    
  17.                     if (startWriteFromWaitingWriter())     
  18.                         return;     
  19.                 } catch (InterruptedException ex) {     
  20.                     cancelledWaitingWriter();     
  21.                     WriterLock.this.notify();     
  22.                     ie = ex;     
  23.                     break;     
  24.                 }     
  25.             }     
  26.         }     
  27.     }     
  28.     if (ie != null) {     
  29.         // Fall through outside synch on interrupt.     
  30.         // On exception, we may need to signal readers.     
  31.         // It is not worth checking here whether it is strictly     
  32.         // necessary.     
  33.         readerLock_.signalWaiters();     
  34.         throw ie;     
  35.     }     
  36. }  

 

再看acquire调用的startWriteFromNewWriter,startWriteFromWaitingWriter和startWrite

Java代码

 

 

  1. protected synchronized boolean startWriteFromNewWriter() {     
  2.     boolean pass = startWrite();     
  3.     //如果不能获得控制权,则等待数+1     
  4.     if (!pass)     
  5.         ++waitingWriters_;     
  6.     return pass;     
  7. }     
  8.     
  9.     
  10. protected synchronized boolean startWrite() {     
  11.     
  12.     // The allowWrite expression cannot be modified without     
  13.     // also changing startWrite, so is hard-wired     
  14.     //是否能获得写控制取决与是否已经有人有控制权(包括读和写)     
  15.     boolean allowWrite = (activeWriter_ == null && activeReaders_ == 0);     
  16.     if (allowWrite)     
  17.         activeWriter_ = Thread.currentThread();     
  18.     return allowWrite;     
  19. }     
  20.     
  21.     
  22. protected synchronized boolean startWriteFromWaitingWriter() {     
  23.     boolean pass = startWrite();     
  24.     //如果能获得控制权,则等待数-1     
  25.     if (pass)     
  26.         --waitingWriters_;     
  27.     return pass;     
  28. }   

 

再来看看WriterLock的release实现

Java代码

 

 

  1. public void release() {     
  2.     Signaller s = endWrite();     
  3.     if (s != null)     
  4.         //如果没有write的waiter,返回的的是readerLock_,则通知所有waiting的reader结束等待     
  5.         //如果有write的waiter,返回的的是writeLock,则通知一个正在等待的writer获得控制权     
  6.         s.signalWaiters();     
  7. }     
  8.     
  9.     
  10. protected synchronized Signaller endWrite() {     
  11.     activeWriter_ = null;     
  12.     //如果没有writer,则通知所有的等待的reader     
  13.     if (waitingReaders_ > 0 && allowReader())     
  14.         return readerLock_;     
  15.     //优先通知一个等待的writer     
  16.     else if (waitingWriters_ > 0)     
  17.         return writerLock_;     
  18.     else    
  19.         return null;     
  20. }  

 

***看看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代码

 

 

  1. /**在activeWriter线程里,有多少次write锁的控制权被获取**/         
  2. protected long writeHolds_ = 0;       
  3.     
  4.  /**存放的每个reader线程共申请获得多少次读控制,key值是thread对象,value是次数**/    
  5. protected HashMap readers_ = new HashMap();     
  6.     
  7. /*一个int的常量缓存*/    
  8. protected static final Integer IONE = new Integer(1);    

 

再来看看Reader部分的重写函数

Java代码

 

 

  1. protected boolean allowReader() {     
  2.     //与父类的变化在于,activeWriter_ == Thread.currentThread(),     
  3.     //也就是说当本线程已经获得写控制的时候,返回true     
  4.     return (activeWriter_ == null && waitingWriters_ == 0) ||     
  5.         activeWriter_ == Thread.currentThread();     
  6. }     
  7.     
  8. protected synchronized boolean startRead() {     
  9.     Thread t = Thread.currentThread();     
  10.     //查看本线程是否已经获得读控制     
  11.     Object c = readers_.get(t);     
  12.     if (c != null) { // already held -- just increment hold count     
  13.         //计数+1     
  14.         readers_.put(t, new Integer(((Integer)(c)).intValue()+1));     
  15.         ++activeReaders_;     
  16.         return true;     
  17.     }     
  18.     //调用allowReader     
  19.     else if (allowReader()) {     
  20.         //将本线程获锁次数纪录在readers_这个map里     
  21.         readers_.put(t, IONE);     
  22.         ++activeReaders_;     
  23.         return true;     
  24.     }     
  25.     else    
  26.         return false;     
  27. }     
  28.     
  29.     
  30. protected synchronized Signaller endRead() {     
  31.     Thread t = Thread.currentThread();     
  32.     Object c = readers_.get(t);     
  33.     if (c == null)     
  34.         throw new IllegalStateException();     
  35.     --activeReaders_;     
  36.     if (c != IONE) { // 多于一个读线程有控制权,more than one hold; 计数递减     
  37.         int h = ((Integer)(c)).intValue()-1;     
  38.         Integer ih = (h == 1)? IONE : new Integer(h);     
  39.         readers_.put(t, ih);     
  40.         return null;     
  41.     }     
  42.     else {     
  43.         readers_.remove(t);     
  44.     
  45.         if (writeHolds_ > 0// 本线程还有写锁在占用控制权     
  46.             return null;     
  47.         //其余与父类实现一样     
  48.         else if (activeReaders_ == 0 && waitingWriters_ > 0)     
  49.             return writerLock_;     
  50.         else    
  51.             return null;     
  52.     }     
  53. }  

 

Reader部分相对于父类的变化在于

* 每个reader试图竞争控制权时,都会将本线程句柄与activeWriter进行比较,相同则认为是可以重入。

* 每个reader线程都在readers_的map里有一个计数器,判断当前有多少次获得reader锁权,释放的时候,只有当计数器为0时才通知其他写线程结束wait。

接着看看Writer部分的重写函数

Java代码

 

 

  1. protected synchronized boolean startWrite() {     
  2.     if (activeWriter_ == Thread.currentThread()) { // 反复重入           
  3.         ++writeHolds_;     
  4.         return true;     
  5.     }     
  6.     else if (writeHolds_ == 0) {     
  7.         //如果没有人在读(activeReaders_==0)和,或者在读的线程和本线程一样,并且readers里没有其他线程     
  8.         if (activeReaders_ == 0 ||      
  9.                 (readers_.size() == 1 &&      
  10.                  readers_.get(Thread.currentThread()) != null)) {     
  11.             //如果本线程在读中,则也可以进入writeLock     
  12.             activeWriter_ = Thread.currentThread();     
  13.             writeHolds_ = 1;     
  14.             return true;     
  15.         }     
  16.         else    
  17.             return false;     
  18.     }     
  19.     else    
  20.         return false;     
  21. }     
  22.     
  23.     
  24. protected synchronized Signaller endWrite() {     
  25.     --writeHolds_;     
  26.     if (writeHolds_ > 0)       
  27.         //这是主要与父类不一样的地方,写锁计数标示仍然有写锁没有被释放     
  28.         return null;     
  29.     else {     
  30.         //与父类一致          
  31.         activeWriter_ = null;     
  32.         if (waitingReaders_ > 0 && allowReader())     
  33.             return readerLock_;     
  34.         else if (waitingWriters_ > 0)     
  35.             return writerLock_;     
  36.         else    
  37.             return null;     
  38.     }     
  39. }  

 

Writer主要与父类不同的地方是

* 通过writeHolds来记录本线程获得锁的次数,以便在release时正确调用通知机制

* 通过当前线程和activeWriter线程比较来实现重入

1.3 EDU.oswego.cs.dl.util.concurrent.ReaderPreferenceReadWriteLock

ReaderPreferenceReadWriteLock类和父类唯一不同就是allowReader方法

Java代码

 

 

  1. protected boolean allowReader() {     
  2.     //只要没有写,就允许读     
  3.     return activeWriter_ == null;     
  4. }   

 

这个类在大多数场景下都不太实用,多数人主要还是使用writer优先锁。

【编辑推荐】

  1. 深入Java核心 Java内存分配原理精讲
  2. 初学Java语言之多态初体验
  3. Java中用接口实现多继承和多态的方法
  4. 浅谈.NET中不一样的多态
责任编辑:金贺 来源: JavaEye博客
相关推荐

2011-11-23 09:39:33

JavaClassLOader机制

2015-09-28 15:59:00

Java动态代理机制

2013-08-02 14:19:50

Java日志缓存

2010-10-13 10:24:38

垃圾回收机制JVMJava

2019-05-27 14:40:43

Java同步机制多线程编程

2017-08-17 15:13:52

PostgreSQL MVCC机制

2017-09-05 10:20:30

PyTorchTensorPython

2010-09-14 09:30:04

Java多态

2010-09-17 13:02:11

JAVA反射机制

2015-10-26 09:25:42

2010-04-26 10:44:27

Oracle SCN

2011-12-15 09:33:19

Java

2014-11-04 10:34:27

JavaCache

2010-01-25 18:24:11

C++

2020-06-30 15:35:36

JavaSPI代码

2023-10-20 09:51:00

编程开发

2011-07-01 15:04:49

Qt 内省

2011-12-12 10:33:47

JavaNIO

2011-12-12 10:19:00

JavaNIO

2010-10-08 10:42:30

点赞
收藏

51CTO技术栈公众号