Java并发编程--深入理解ReadWriteLock锁机制

Lock锁简介

Lock和synchronized 同步代码块类似,是线程同步机制,但是Lock比synchronized 同步代码块更加灵活。Lock是通过synchronized关键字来实现的。从Java 5开始,java.util.concurrent.locks包含几个锁,下面通过自己实现的Lock来看看Lock的实现原理。

简单的锁实现

public class Lock{

  private boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }

  public synchronized void unlock(){
    isLocked = false;
    notify();
  }
}

上面的这个简单的锁,是一个不可重入的锁。

可重入锁实现Lock Reentrance

Java中synchronized修饰的同步代码块是可重入的,也就是说,当前进程获取到了同步监视器后,可以进入到其他同步代码块中。下面是一个例子:

public class Reentrant{

  public synchronized outer(){
    inner();
    //inner 是同一个监视器this下的同步代码块,所以可以直接从outer中进入。
  }

  public synchronized inner(){
    //do something
  }
}

上面代码中当前进程获取到监视器时,可以直接由outer同步代码块进入到inner同步代码块。但是使用Lock时就会出现问题:

public class Reentrant2{

  Lock lock = new Lock();
  public outer(){
    lock.lock();
    inner();
    lock.unlock();
  }

  public synchronized inner(){
    lock.lock();
    //do something
    lock.unlock();
  }
}

线程调用outer()将首先lock Lock实例。然后会调用 inner()。在该inner()方法内,线程将再次尝试lock Lock实例。这里会出现问题,因为Lock实例已被锁定在outer()方法中。看看Lock的代码实现:

public class Lock{

  boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }
  ...
}

为了使Lock可重入做如下改变:

public class Lock{

  boolean isLocked = false;
  Thread  lockedBy = null;
  int     lockedCount = 0;

  public synchronized void lock() throws InterruptedException{

    Thread callingThread = Thread.currentThread();
    //如果是同一个线程调用可重入。
    while(isLocked && lockedBy != callingThread){
      wait();
    }
    isLocked = true;
    lockedCount++;
    lockedBy = callingThread;
  }


  public synchronized void unlock(){
    if(Thread.curentThread() == this.lockedBy){
      lockedCount--;

      if(lockedCount == 0){
        isLocked = false;
        notify();
      }
    }
  }

  ...
}

上面的Lock支持同一个线程多次进入锁。

ReadWriteLock实现

首先总结获取资源的reader 和writer权限的条件:
①reader权限 如果没有线程正在写入,并且没有线程请求写访问。
②writer权限 如果没有线程正在读取或写入。
如果一个线程想要读取资源,只要没有线程正在写,没有线程请求对资源的写入访问就可以了。通过优先处理写访问请求,我们假设写请求比读请求更重要。此外,如果读数是最常发生的情况,而且我们没有提高写入优先级,可能会出现饥饿。要求写访问的线程将被阻止,直到所有读者解锁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 有两个 lock 方法和两个 unlock 方法. 其中一个 lock 和unlock 方法对应read 权限另一个 lock 和unlock 方法对应 write 权限。
lockRead()中实现了读权限的控制. 当没有线程writer的时候,才可以进行读操作。

可重入的ReadWriteLock锁

上面的ReadWriteLock是不可重入的,当一个线程获取到writer权限后,再次获取一个writer权限时或发生阻塞,考虑一下情形。

①Thread 1 获得read 权限。

②Thread 2 请求获得write 权限因为Thread 1 在读所以线程2会被阻塞。

③Thread 1 再次请求 read 权限(重新进入lock), 因为Thread 2 在请求writer所以Thread 1会被阻塞。

Read 可重入锁

为了使 ReadWriteLock 对 readers 可重入先建立规则:当一个线程已经获取到read权限的时候可以重新获取reader权限。
为了确定一个线程是否已经获取到了读权限,可以用一个HashMap来保存线程和reader次数。

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;
  }

}

writer可重入锁

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;
  }
}

参考文献

http://tutorials.jenkov.com/java-concurrency/read-write-locks.html

你可能感兴趣的:(java)