读-写不能够存活,大家仍然应当明白其促成背后的原理

Java5 在 java.util.concurrent
包中早就包罗了读写锁。固然如此,我们照旧应当掌握其完成背后的规律。

以下内容转自http://ifeve.com/read-write-locks/

  1. 读/写锁的 Java 实现(Read / Write Lock Java Implementation)
  2. 读/写锁的重入(Read / Write Lock Reentrance)
  3. 读锁重入(Read Reentrance)
  4. 写锁重入(Write Reentrance)
  5. 读锁升级到写锁(Read to Write Reentrance)
  6. 写锁降级到读锁(Write to Read Reentrance)
  7. 可重入的 ReadWriteLock 的完整兑现(Fully Reentrant ReadWriteLock)
  8. 在 finally 中调用 unlock() (Calling unlock() from a finally-clause)

比较Java中的锁(Locks in
Java)里Lock达成,读写锁更复杂一些。假使你的次第中关系到对有的共享能源的读和写操作,且写操作没有读操作那么频繁。在并未写操作的时候,四个线程同时读三个财富没有别的难题,所以理应允许多少个线程能在同时读取共享能源。不过倘诺有一个线程想去写那几个共享能源,就不应该再有任何线程对该能源拓展读或写(译者注:也正是说:读-读能存活,读-写无法存活,写-写不可能存活)。那就需求一个读/写锁来化解那些题材。

读/写锁的 Java 实现

先让大家对读写访问财富的准绳做个概述:

读取 没有线程正在做写操作,且没有线程在央求写操作。

写入 没有线程正在做读写操作。

即使有些线程想要读取能源,只要没有线程正在对该财富拓展写操作且没有线程请求对该财富的写操作即可。大家假如对写操作的伸手比对读操作的请求更首要,就要晋升写请求的先期级。其余,要是读操作发生的可比频仍,大家又从不升级写操作的优先级,那么就会生出“饥饿”现象。请求写操作的线程会一向不通,直到全部的读线程都从
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 类中,读锁和写锁各有2个得到锁和释放锁的点子。

读锁的贯彻在
lockRead()中,只要没有线程拥有写锁(writers==0),且并未线程在哀求写锁(writeRequests
==0),全数想博得读锁的线程都能不负众望获取。

写锁的落实在
lockWrite()中,当3个线程想取得写锁的时候,首先会把写锁请求数加
1(writeRequests++),然后再去看清是不是能够真能得到写锁,当没有线程持有读锁(readers==0
),且并未线程持有写锁(writers==0)时就能赢得写锁。有稍许线程在呼吁写锁并非亲非故系。

须要小心的是,在五个释放锁的章程(unlockRead,unlockWrite)中,都调用了
notifyAll 方法,而不是
notify。要分解那几个缘故,大家能够设想上边一种情景:

借使有线程在守候获取读锁,同时又有线程在等候获取写锁。要是此时在那之中一个等待读锁的线程被
notify
方法唤醒,但因为此时仍有请求写锁的线程存在(writeRequests>0),所以被唤起的线程会再度进入阻塞状态。可是,等待写锁的线程3个也没被升迁,就像是什么也没产生过同样(译者注:信号丢失现象)。如若用的是
notifyAll 方法,全体的线程都会被唤起,然后判断是还是不是获得其请求的锁。

用 notifyAll
还有3个利益。如若有四个读线程在伺机读锁且没无线程在伺机写锁时,调用
unlockWrite()后,所有等待读锁的线程都能马上成功获得读锁 ——
而不是3回只同意3个。

Java5在java.util.concurrent包中曾经包括了读写锁。纵然如此,我们照旧应当明白其促成背后的原理。

读/写锁的重入

地点完结的读/写锁(ReadWriteLock)
是不可重入的,当贰个早已持有写锁的线程再一次伸手写锁时,就会被堵塞。原因是一度有三个写线程了——正是它本人。别的,考虑上面的事例:

  1. Thread 1 得到了读锁。
  2. Thread 2 请求写锁,但因为 Thread 1 持有了读锁,所以写锁请求被堵塞。
  3. Thread 1 再想呼吁三回读锁,但因为 Thread 2
    处于请求写锁的情状,所以想再也赢得读锁也会被堵塞。
    下面那种景况使用前边的 ReadWriteLock
    就会被锁定——一种恍若于死锁的情形。不会再有线程能够成功收获读锁或写锁了。

为了让 ReadWriteLock
可重入,须要对它做一些更上一层楼。上面会分别处理读锁的重入和写锁的重入。

以下是本文的大旨

读锁重入

为了让 ReadWriteLock 的读锁可重入,大家要先为读锁重入建立规则:

要保管有些线程中的读锁可重入,要么知足获取读锁的规则(没有写或写请求),要么已经具有读锁(不管是还是不是有写请求)。
要分明2个线程是不是已经具备读锁,能够用三个 map
来储存已经持有读锁的线程以及相应线程获取读锁的次数,当供给看清有些线程能不可能获取读锁时,就使用
map 中储存的数据进行判定。上边是办法 lockRead 和 unlockRead
修改后的的代码:

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();

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

代码中大家能够见见,唯有在尚未线程拥有写锁的意况下才允许读锁的重入。其它,重入的读锁比写锁优先级高。

读/写锁的Java实现

写锁重入

仅当3个线程已经颇具写锁,才同意写锁重入(再度赢得写锁)。上边是办法
lockWrite 和 unlockWrite 修改后的的代码。

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();

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

只顾在明确当前线程是或不是能够收获写锁的时候,是什么处理的。

先让大家对读写访问财富的原则做个概述:

读锁升级到写锁

有时候,大家愿意贰个全部读锁的线程,也能获得写锁。想要允许那样的操作,供给这么些线程是绝无仅有一个享有读锁的线程。writeLock()要求做点改动来达到这些指标:

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();

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

当今 ReadWriteLock 类就足以从读锁升级到写锁了。

读取-没有线程正在做写操作,且尚未线程在央浼写操作。

写锁降级到读锁

有时有着写锁的线程也指望取得读锁。若是一个线程拥有了写锁,那么自然别的线程是不或许持有读锁或写锁了。所以对于二个富有写锁的线程,再赢得读锁,是不会有何样危险的。大家仅仅须求对下边can格兰特ReadAccess 方法开始展览简要地修改:

public class ReadWriteLock{
    private boolean canGrantReadAccess(Thread callingThread){
        if(isWriter(callingThread)) return true;
        if(writingThread != null) return false;
        if(isReader(callingThread) return true;
        if(writeRequests > 0) return false;
        return true;
    }
}

写入-没有线程正在做读写操作。

可重入的 ReadWriteLock 的完整兑现

上面是全体的 ReadWriteLock
实现。为了方便代码的读书与精通,不难对上面的代码做了重构。重构后的代码如下。

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();

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

要是某些线程想要读取能源,只要没有线程正在对该能源举办写操作且没有线程请求对该财富的写操作即可。大家如若对写操作的央求比对读操作的央求更关键,就要进步写请求的事先级。别的,假如读操作爆发的相比较频仍,大家又没有升级写操作的优先级,那么就会发出“饥饿”现象。请求写操作的线程会一向不通,直到全部的读线程都从ReadWriteLock上解锁了。假设间接保证新线程的读操作权限,那么等待写操作的线程就会直接不通下去,结果正是发生“饥饿”。因而,只有当没有线程正在锁住ReadWriteLock进行写操作,且从未线程请求该锁准备实施写操作时,才能保险读操作继续。

在 finally 中调用 unlock()

在行使 ReadWriteLock 来保险临界区时,如若临界区只怕抛出11分,在 finally
块中调用 readUnlock()和 writeUnlock()就显得很首要了。那样做是为着确定保障ReadWriteLock 能被成功解锁,然后此外线程能够请求到该锁。那里有个例子:

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

地点那样的代码结构能够确认保证临界区中抛出特别时 ReadWriteLock
也会被假释。借使 unlockWrite 方法不是在 finally
块中调用的,当临界区抛出了格外时,ReadWriteLock
会一贯保持在写锁定状态,就会促成全数调用 lockRead()或
lockWrite()的线程一直不通。唯一能够再一次解锁 ReadWriteLock 的因素恐怕正是ReadWriteLock
是可重入的,当抛出至极时,那几个线程后续仍是能够成功赢得那把锁,然后实施临界区以及重复调用
unlockWrite(),那就会再也出狱
ReadWriteLock。可是只要该线程后续不再获得那把锁了呢?所以,在 finally
中调用 unlockWrite 对写出健康代码是很重庆大学的。

当其余线程没有对共享财富举办读操作依然写操作时,有个别线程就有大概获得该共享能源的写锁,进而对共享财富举办写操作。有微微线程请求了写锁以及以何种顺序请求写锁并不重大,除非你想保障写锁请求的公平性。

依据下边包车型地铁描述,不难的落到实处出二个读/写锁,代码如下

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()中,只要没有线程拥有写锁(writers==0),且没有线程在伸手写锁(writeRequests
==0),全部想赢得读锁的线程都能不负众望收获。

写锁的落到实处在lockWrite()中,当两个线程想取得写锁的时候,首先会把写锁请求数加1(writeRequests++),然后再去看清是不是能够真能博取写锁,当没有线程持有读锁(readers==0
),且并未线程持有写锁(writers==0)时就能取得写锁。有稍许线程在伸手写锁并非亲非故联。

内需留意的是,在四个释放锁的法子(unlockRead,unlockWrite)中,都调用了notifyAll方法,而不是notify。要解释这一个原因,我们得以想像上面一种状态:

假诺无线程在等候获取读锁,同时又有线程在守候获取写锁。假诺那时候其中2个等候读锁的线程被notify方法唤醒,但因为那时仍有请求写锁的线程存在(writeRequests>0),所以被提示的线程会再一次进入阻塞状态。然则,等待写锁的线程三个也没被唤起,就如什么也没发生过千篇一律(译者注:信号丢失现象)。倘诺用的是notifyAll方法,全数的线程都会被提示,然后判断是还是不是取得其请求的锁。

用notifyAll还有两个好处。借使有多少个读线程在等候读锁且没有线程在等候写锁时,调用unlockWrite()后,全数等待读锁的线程都能马上成功收获读锁
—— 而不是贰回只允许二个。

读/写锁的重入

下面达成的读/写锁(ReadWriteLock)
是不可重入的,当四个一度有所写锁的线程再一次呼吁写锁时,就会被封堵。原因是曾经有二个写线程了——就是它和谐。其它,考虑上面包车型地铁例证:

  1. Thread 1获得了读锁
  2. Thread 2请求写锁,但因为Thread 1持有了读锁,所以写锁请求被卡住。
  3. Thread 1再想请求3遍读锁,但因为Thread
    2处于请求写锁的处境,所以想再次取得读锁也会被堵塞。

地点那种意况使用后面的ReadWriteLock就会被锁定——一种恍若于死锁的气象。不会再有线程可以成功得到读锁或写锁了。

为了让ReadWriteLock可重入,须求对它做一些更上一层楼。上边会分别处理读锁的重入和写锁的重入。

读锁重入

为了让ReadWriteLock的读锁可重入,我们要先为读锁重入建立规则:

  • 要保证某些线程中的读锁可重入,要么满足获取读锁的标准化(没有写或写请求),要么已经具备读锁(不管是还是不是有写请求)。

要规定三个线程是还是不是业已具有读锁,能够用1个map来存款和储蓄已经具备读锁的线程以及相应线程获取读锁的次数,当需求判定有些线程能无法得到读锁时,就使用map中存款和储蓄的多寡进行判定。上边是格局lockRead和unlockRead修改后的的代码:

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();

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

代码中我们能够看看,惟有在没有线程拥有写锁的状态下才允许读锁的重入。其余,重入的读锁比写锁优先级高。

写锁重入

仅当3个线程已经颇具写锁,才同意写锁重入(再一次获得写锁)。上面是方法lockWrite和unlockWrite修改后的的代码。

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();

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

小心在鲜明当前线程是还是不是能够拿走写锁的时候,是什么处理的。

读锁升级到写锁

突发性,大家意在3个富有读锁的线程,也能赢得写锁。想要允许那样的操作,须求那个线程是唯一3个具备读锁的线程。writeLock()必要做点改动来完结那几个目标:

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();

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

近期ReadWriteLock类就足以从读锁升级到写锁了。

写锁降级到读锁

有时有着写锁的线程也希望赢得读锁。假如贰个线程拥有了写锁,那么自然别的线程是不恐怕具备读锁或写锁了。所以对于3个具备写锁的线程,再获得读锁,是不会有啥危险的。大家唯有供给对上边can格兰特ReadAccess方法实行简短地修改:

public class ReadWriteLock{
    private boolean canGrantReadAccess(Thread callingThread){
        if(isWriter(callingThread)) return true;
        if(writingThread != null) return false;
        if(isReader(callingThread) return true;
        if(writeRequests > 0) return false;
        return true;
    }
}

可重入的ReadWriteLock的总体兑现

下边是完全的ReadWriteLock达成。为了方便代码的开卷与理解,简单对上边的代码做了重构。重构后的代码如下。

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();

    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中调用unlock()

在使用ReadWriteLock来保卫安全临界区时,借使临界区大概抛出特别,在finally块中调用readUnlock()和writeUnlock()就突显很重庆大学了。那样做是为着保障ReadWriteLock能被成功解锁,然后其它线程能够请求到该锁。那里有个例子:

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

地点那样的代码结构能够保险临界区中抛出格外时ReadWriteLock也会被放走。如若unlockWrite方法不是在finally块中调用的,当临界区抛出了尤其时,ReadWriteLock会一向维持在写锁定状态,就会导致全数调用lockRead()或lockWrite()的线程一向不通。唯一能够再一次解锁ReadWriteLock的要素恐怕正是ReadWriteLock是可重入的,当抛出十一分时,那一个线程后续还能成功收获这把锁,然后实施临界区以及重新调用unlockWrite(),那就会重复放出ReadWriteLock。但是倘使该线程后续不再获得那把锁了吗?所以,在finally中调用unlockWrite对写出健康代码是很关键的。

相关文章