java concurrency <读写锁>

java concurrency <读写锁>

背景

读/写锁比Lock中的Lock所示的Lock实现更复杂。 想象一下,你有一个应用程序来读取和写入一些资源,但写入它并不像阅读那样多。 读取相同资源的两个线程不会导致彼此的问题,因此要同时读取资源的多个线程被同时访问,重叠。 但是,如果单个线程想要写入资源,则不得不同时进行其他读取和写入操作。 为了解决允许多个读取器但只有一个写入器的问题,您将需要读/写锁定。

Java 5在java.util.concurrent包中附带了读/写锁实现。 即使如此,了解其实施背后的理论可能还是有用的。

java中读写锁的实现

我们假设下面两个条件成立:

  • Read Access 没有线程在读 没有线程请求读
  • Write Access 没有线程在读或者是写

如果一个线程想要读取资源,只要没有线程写入它,没有线程请求对资源的写入访问就可以了。 通过对写访问请求进行优先级排序,我们假设写请求比读请求更重要。 此外,如果读数是最常发生的情况,而且我们没有提高写入优先级,可能会出现饥饿。 要求写访问的线程将被阻止,直到所有读线程解锁了ReadWriteLock。 如果不断授予新线程读访问权限,则等待写入访问的线程将保持不间断地被阻止,导致饥饿。 因此,如果没有线程当前锁定ReadWriteLock进行写入,或者请求它锁定写入,线程只能被授予读访问权限。

如果没有线程正在读取或写入资源,那么可以授予一个需要对资源进行写入访问的线程。 除非要保证请求写访问的线程之间的公平性否则无论多少线程请求写访问或按什么顺序并不重要。

考虑到这些简单的规则,我们可以实现一个ReadWriteLock,如下所示:

public class ReadWriteLock{

  private int readers       = 0;
  private int writers       = 0;
  private int writeRequests = 0;

  public synchronized void lockRead() throws InterruptedException{
    while(writers > 0 || writeRequests > 0){
      wait();
    }
    readers++;
  }

  public synchronized void unlockRead(){
    readers--;
    notifyAll();
  }

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;

    while(readers > 0 || writers > 0){
      wait();
    }
    writeRequests--;
    writers++;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writers--;
    notifyAll();
  }
}

ReadWriteLock有两个锁定方法和两个解锁方法。一种用于读取访问的锁定和解锁方法,并且一个锁定和解锁用于写访问。

读访问规则在lockRead()方法中实现。所有的线程都可以读取访问权限,除非有一个线程具有写访问权限,或者一个或多个线程请求写入权限。

写访问规则在lockWrite()方法中实现。需要写入访问的线程通过请求写访问(writeRequests ++)开始。那么它将检查它是否可以实际获得写入权限。如果没有线程具有对资源的读取访问权限,并且没有对该资源的写访问权限的线程,则线程可以获得写访问权限。有多少线程请求写访问并不重要。

值得注意的是unlockRead()和unlockWrite()都调用notifyAll()而不是notify()。为了解释为什么,想像下面的情况:

ReadWriteLock内部有线程等待读访问,线程等待写访问。如果由notify()唤醒的线程是读取访问线程,则会被等待,因为有等待写入访问的线程。然而,等待写入访问的线程都没有被唤醒,所以没有更多的事情发生。没有线程不会读取和写入访问。通过调用noftifyAll(),所有等待的线程都被唤醒,并检查是否可以获得所需的访问。

调用notifyAll()也有另一个优势。如果多个线程正在等待读取访问,并且无法进行写入访问,并且unlockWrite()被调用,那么等待读取访问的所有线程都将被一次授予读取访问权限。

读写锁的重入

前面的ReadWriteLock不支持线程的重入,如果一个写的线程重新请求,它会阻塞,因为他已经获得了这个锁。考虑到将来,我们应该关注如下信息:

  • 线程获取到读的权限。
  • 线程2请求写阻塞,因为线程1在读
  • 线程1重新读阻塞,因为有一个线程在写。

在这种情况下,以前的ReadWriteLock将锁定 - 类似于死锁的情况。 没有线程请求既不读取也不写入访问权限。

为了使ReadWriteLock可重入,有必要进行一些更改。 读者和作家的重读将分开处理。

读的重入

为了使ReadWriteLock可读入读者,我们将首先建立读取重入的规则:

  • 如果线程可以获得读取访问权限(无写入或写入请求),或者如果已经具有读取访问权限(无论写入请求)),线程都将被授予读取重入。

为了确定线程是否具有读访问权限,已授予对授予读访问权限的每个线程的引用,以及映射中获取的读锁定次数。 当确定是否可以授予读访问权限时,将检查此Map对引用线程的引用。 下面是lockRead()和unlockRead()方法如何看待这个更改:

public class ReadWriteLock{

  private Map readingThreads =
      new HashMap();

  private int writers        = 0;
  private int writeRequests  = 0;

  public synchronized void lockRead() throws InterruptedException{
    Thread callingThread = Thread.currentThread();
    while(! canGrantReadAccess(callingThread)){
      wait();                                                                   
    }

    readingThreads.put(callingThread,
       (getAccessCount(callingThread) + 1));
  }


  public synchronized void unlockRead(){
    Thread callingThread = Thread.currentThread();
    int accessCount = getAccessCount(callingThread);
    if(accessCount == 1){ readingThreads.remove(callingThread); }
    else { readingThreads.put(callingThread, (accessCount -1)); }
    notifyAll();
  }


  private boolean canGrantReadAccess(Thread callingThread){
    if(writers > 0)            return false;
    if(isReader(callingThread) return true;
    if(writeRequests > 0)      return false;
    return true;
  }

  private int getReadAccessCount(Thread callingThread){
    Integer accessCount = readingThreads.get(callingThread);
    if(accessCount == null) return 0;
    return accessCount.intValue();
  }

  private boolean isReader(Thread callingThread){
    return readingThreads.get(callingThread) != null;
  }

}

写的重入

public class ReadWriteLock{

    private Map readingThreads =
        new HashMap();

    private int writeAccesses    = 0;
    private int writeRequests    = 0;
    private Thread writingThread = null;

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;
    Thread callingThread = Thread.currentThread();
    while(! canGrantWriteAccess(callingThread)){
      wait();
    }
    writeRequests--;
    writeAccesses++;
    writingThread = callingThread;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writeAccesses--;
    if(writeAccesses == 0){
      writingThread = null;
    }
    notifyAll();
  }

  private boolean canGrantWriteAccess(Thread callingThread){
    if(hasReaders())             return false;
    if(writingThread == null)    return true;
    if(!isWriter(callingThread)) return false;
    return true;
  }

  private boolean hasReaders(){
    return readingThreads.size() > 0;
  }

  private boolean isWriter(Thread callingThread){
    return writingThread == callingThread;
  }
}
Notice how the thread currently holding the write lock is now taken into account when determining if the calling thread can get write access.

Read to Write Reentrance
Sometimes it is necessary for a thread that have read access to also obtain write access. For this to be allowed the thread must be the only reader. To achieve this the writeLock() method should be changed a bit. Here is what it would look like:

public class ReadWriteLock{

    private Map readingThreads =
        new HashMap();

    private int writeAccesses    = 0;
    private int writeRequests    = 0;
    private Thread writingThread = null;

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;
    Thread callingThread = Thread.currentThread();
    while(! canGrantWriteAccess(callingThread)){
      wait();
    }
    writeRequests--;
    writeAccesses++;
    writingThread = callingThread;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    writeAccesses--;
    if(writeAccesses == 0){
      writingThread = null;
    }
    notifyAll();
  }

  private boolean canGrantWriteAccess(Thread callingThread){
    if(isOnlyReader(callingThread))    return true;
    if(hasReaders())                   return false;
    if(writingThread == null)          return true;
    if(!isWriter(callingThread))       return false;
    return true;
  }

  private boolean hasReaders(){
    return readingThreads.size() > 0;
  }

  private boolean isWriter(Thread callingThread){
    return writingThread == callingThread;
  }

  private boolean isOnlyReader(Thread thread){
      return readers == 1 && readingThreads.get(callingThread) != null;
      }
  
}

完整的重入

public class ReadWriteLock{

  private Map readingThreads =
       new HashMap();

   private int writeAccesses    = 0;
   private int writeRequests    = 0;
   private Thread writingThread = null;


  public synchronized void lockRead() throws InterruptedException{
    Thread callingThread = Thread.currentThread();
    while(! canGrantReadAccess(callingThread)){
      wait();
    }

    readingThreads.put(callingThread,
     (getReadAccessCount(callingThread) + 1));
  }

  private boolean canGrantReadAccess(Thread callingThread){
    if( isWriter(callingThread) ) return true;
    if( hasWriter()             ) return false;
    if( isReader(callingThread) ) return true;
    if( hasWriteRequests()      ) return false;
    return true;
  }


  public synchronized void unlockRead(){
    Thread callingThread = Thread.currentThread();
    if(!isReader(callingThread)){
      throw new IllegalMonitorStateException("Calling Thread does not" +
        " hold a read lock on this ReadWriteLock");
    }
    int accessCount = getReadAccessCount(callingThread);
    if(accessCount == 1){ readingThreads.remove(callingThread); }
    else { readingThreads.put(callingThread, (accessCount -1)); }
    notifyAll();
  }

  public synchronized void lockWrite() throws InterruptedException{
    writeRequests++;
    Thread callingThread = Thread.currentThread();
    while(! canGrantWriteAccess(callingThread)){
      wait();
    }
    writeRequests--;
    writeAccesses++;
    writingThread = callingThread;
  }

  public synchronized void unlockWrite() throws InterruptedException{
    if(!isWriter(Thread.currentThread()){
      throw new IllegalMonitorStateException("Calling Thread does not" +
        " hold the write lock on this ReadWriteLock");
    }
    writeAccesses--;
    if(writeAccesses == 0){
      writingThread = null;
    }
    notifyAll();
  }

  private boolean canGrantWriteAccess(Thread callingThread){
    if(isOnlyReader(callingThread))    return true;
    if(hasReaders())                   return false;
    if(writingThread == null)          return true;
    if(!isWriter(callingThread))       return false;
    return true;
  }


  private int getReadAccessCount(Thread callingThread){
    Integer accessCount = readingThreads.get(callingThread);
    if(accessCount == null) return 0;
    return accessCount.intValue();
  }


  private boolean hasReaders(){
    return readingThreads.size() > 0;
  }

  private boolean isReader(Thread callingThread){
    return readingThreads.get(callingThread) != null;
  }

  private boolean isOnlyReader(Thread callingThread){
    return readingThreads.size() == 1 &&
           readingThreads.get(callingThread) != null;
  }

  private boolean hasWriter(){
    return writingThread != null;
  }

  private boolean isWriter(Thread callingThread){
    return writingThread == callingThread;
  }

  private boolean hasWriteRequests(){
      return this.writeRequests > 0;
  }

}

最后记得在finally块中调用lock

lock.lockWrite();
try{
  //do critical section code, which may throw exception
} finally {
  lock.unlockWrite();
}

你可能感兴趣的:(java concurrency <读写锁>)