Concurrent包源码解读之ReentrantLock,ReentrantReadWriteLock

ReentrantLock概述

 ReentrantLock是一种可重入互斥锁,ReentrantLock的实现可以说是对AbstractQueuedSynchronizer–AQS运用的直接实现,如果理解了AQS的各种锁获取模式的理解,可以说对于ReentrantLock的理解已经成功了一大半。
 ReentrantLock的实现实现了两种获取锁的机制,公平锁和非公平锁。ReentrantLock根据传入构造方法的布尔型参数实例化出Sync的实现类FairSync和NonfairSync,分别表示公平的Sync和非公平的Sync。ReentrantLock的比较多的是非公平锁。
 重入锁ReetrantLock,JDK 1.5新增的类,实现了Lock接口,作用与synchronized关键字相当,但比synchronized更加灵活。ReetrantLock本身也是一种支持重进入的锁,即该锁可以支持一个线程对资源重复加锁,同时也支持公平锁与非公平锁。所谓的公平与非公平指的是在请求先后顺序上,先对锁进行请求的就一定先获取到锁,那么这就是公平锁,反之,如果对于锁的获取并没有时间上的先后顺序,如后请求的线程可能先获取到锁,这就是非公平锁,一般而言非,非公平锁机制的效率往往会胜过公平锁的机制,但在某些场景下,可能更注重时间先后顺序,那么公平锁自然是很好的选择。需要注意的是ReetrantLock支持对同一线程重加锁,但是加锁多少次,就必须解锁多少次,这样才可以成功释放锁。

ReentrantLock的内部同步方式实现

ReentrantLock的构造函数

 分析ReentrantLock的源码,先从它的构造函数入手,它实现了两个构造函数。一个是默认的构造函数,默认是创建非公平锁,带参数的构造函数根据参数的boolean值决定创建公平锁还是非公平锁,如果传入的值为false,就会创建公平锁。如果传入的为true,就会创建非公平锁。

    private final Sync sync;

    //默认构造,创建非公平锁NonfairSync
    public ReentrantLock() {   
        sync = new NonfairSync();
    }
    //根据传入参数创建锁类型
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock的内部实现

 ReentrantLock的内部定义了三个静态内部类Sync,NonfairSync,FairSync的继承关系如下:

同步方式继承图

同步方式继承图

 首先看Sync的实现如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;


        abstract void lock();

         //nonfairTryAcquire方法
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //判断同步状态是否为0,并尝试再次获取同步状态
            if (c == 0) {
                 //执行CAS操作
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
             //如果当前线程已获取锁,属于重入锁,再次获取锁后将status值加1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                 //设置当前同步状态,当前只有一个线程持有锁,因为不会发生线程安全问题,可以直接执行 setState(nextc);    
                setState(nextc);
                return true;
            }
            return false;
        }

        //释放锁
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

       .....
    }

 从代码执行流程可以看出,这里做了两件事,一是尝试再次获取同步状态,如果获取成功则将当前线程设置为OwnerThread,否则失败,二是判断当前线程current是否为OwnerThread,如果是则属于重入锁,state自增1,并获取锁成功,返回true,反之失败,返回false,也就是tryAcquire(arg)执行失败,返回false。需要注意的是nonfairTryAcquire(int acquires)内部使用的是CAS原子性操作设置state值,可以保证state的更改是线程安全的,因此只要任意一个线程调用nonfairTryAcquire(int acquires)方法并设置成功即可获取锁,不管该线程是新到来的还是已在同步队列的线程,毕竟这是非公平锁,并不保证同步队列中的线程一定比新到来线程请求(可能是head结点刚释放同步状态然后新到来的线程恰好获取到同步状态)先获取到锁,这点跟后面还会讲到的公平锁不同。
 Sync是一个抽象类,它的继承自AQS,前面的关于的AQS的博客已经分析过,使用AQS是需要实现定义的锁获取和释放的抽象方法,Sync关于锁获取使用了两种方式:

        //获取锁的抽象方法,又子类实现
        abstract void lock();

       //自定义的获取锁的方法,从方法名字可以看出,这种方式是以抢占的方式获取锁,是一种非公平获取锁的方式
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 也就是说Sync定义了两种方法获取锁供子类实现或者调用。接下来看的它的子类NonfairSync非公平同步锁实现如下:

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        //获取锁的方式直接修改state值为1,并被当前线程独占
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        //获取锁的方式直接调用Sync的nonfairTryAcquire
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

 另外一个子类FairSync为公平锁实现的同步方案代码如下:

        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        //获取锁之前会判断有没有在等待队列中的前继线程,以一种公平的方式获取
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 可以说,NonfairSync和FairSync对于获取锁都实现了两种方式,本质都是以非公平方式或者公平方式获取锁。要理解这两个类的实现,只需要读懂AQS框架的源码,可以说,理解起来非常简单。
 再回到刚刚开始分析的ReentrantLock的构造函数,依据传入的构造函数来决定ReentrantLock使用公平的锁获取方案还是非公平的锁获取方案。关于ReentrantLock内部的实现的方法,获取锁和释放锁的方式如下:

    //不响应中断的:非公平独占锁或者公平独占锁
    public void lock() {
        sync.lock();
    }

    //响应中断的:非公平独占锁或者公平独占锁
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    //不响应中断的:非公平独占锁
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    //响应中断的带有超时机制的:非公平独占锁或者公平独占锁
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    //释放锁
    public void unlock() {
        sync.release(1);
    }

    //创建一个Condition
    public Condition newCondition() {
        return sync.newCondition();
    }

    //获取当前独占锁的线程持有的state的数值
    public int getHoldCount() {
        return sync.getHoldCount();
    }

    //判断锁是否被当前线程持有
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    //判断锁是否被线程持有
    public boolean isLocked() {
        return sync.isLocked();
    }

    //判断是否是公平锁
    public final boolean isFair() {
        return sync instanceof FairSync;
    }

    //获取当前持有锁的线程
    protected Thread getOwner() {
        return sync.getOwner();
    }

    //当前“等待锁的线程队列CLH”中是否有节点
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    //判断给定线程是否在当前“等待锁的线程队列CLH”中
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }

    //获取当前“等待锁的线程队列CLH”的总长度
    public final int getQueueLength() {
        return sync.getQueueLength();
    }

    //获取当前“等待锁的线程队列CLH”中所有等待的线程
    protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }

    //判断在当前“等待锁的线程队列CLH”中是否有线程等待的原因是掌握在给定的Condition手中
    public boolean hasWaiters(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    //获取在当前“等待锁的线程队列CLH”中线程等待的原因是掌握在给定的Condition手中的等待线程长度
    public int getWaitQueueLength(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    //获取在当前“等待锁的线程队列CLH”中线程等待的原因是掌握在给定的Condition手中的所有等待线程
    protected Collection<Thread> getWaitingThreads(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

 都是调用内部类的实现。

总结

公平锁和非公平锁只有两处不同:

 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

 相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

ReentrantReadWriteLock的实现概述

 当分析ReentranctReadWriteLock时,或者说分析内部使用AQS实现的工具类时,需要明白的就是AQS的state代表的是什么。ReentrantLockReadWriteLock中的state同时表示写锁和读锁的个数。为了实现这种功能,state的高16位表示读锁的个数,低16位表示写锁的个数。AQS有两种模式:共享模式和独占模式,读写锁的实现中,读锁使用共享模式;写锁使用独占模式;另外一点需要记住的即使,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁。
 ReentrantReadWriteLock是同时包含排它锁和共享锁特性的一种锁,这里主要以ReentrantReadWriteLock为例来进行分析学习。我们使用ReentrantReadWriteLock的写锁时,使用的便是排它锁的特性;使用ReentrantReadWriteLock的读锁时,使用的便是共享锁的特性。

ReentrantReadWriteLock的内部同步方式实现

 ReentrantReadWriteLock有一个读锁(ReadLock)和一个写锁(WriteLock)属性,分别代表可重入读写锁的读锁和写锁。

通过构造函数来看,

public ReentrantReadWriteLock(boolean fair) {
        sync = (fair)? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

 从上面可以看到,构造方法决定了Sync是FairSync还是NonfairSync。Sync继承了AbstractQueuedSynchronizer,而Sync是一个抽象类,NonfairSync和FairSync继承了Sync,并重写了其中的抽象方法。在创建读锁和写锁对象的时候,会把这个可重入的读写锁上的Sync属性传递过去。

protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

Sync分析

 Sync中提供了很多方法,但是有两个方法是抽象的,子类必须实现。下面以FairSync为例,分析一下这两个抽象方法:

static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

 writerShouldBlock和readerShouldBlock方法都表示当有别的线程也在尝试获取锁时,是否应该阻塞。
对于公平模式,hasQueuedPredecessors()方法表示前面是否有等待线程。一旦前面有等待线程,那么为了遵循公平,当前线程也就应该被挂起。
下面再来看NonfairSync的实现:

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }

 从上面可以看到,非公平模式下,writerShouldBlock直接返回false,说明不需要阻塞;而readShouldBlock调用了apparentFirstQueuedIsExcluisve()方法。该方法在当前线程是写锁占用的线程时,返回true;否则返回false。也就说明,如果当前有一个写线程正在写,那么该读线程应该阻塞。
继承AQS的类都需要使用state变量代表某种资源,ReentrantReadWriteLock中的state代表了读锁的数量和写锁的持有与否,
可以看到state的高16位代表读锁的个数;低16位代表写锁的状态。

获取锁

读锁的获取

当需要使用读锁时,首先调用lock方法,如下:

public void lock() {
            sync.acquireShared(1);
        }

 从代码可以看到,读锁使用的是AQS的共享模式,AQS的acquireShared方法如下:

if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);

当tryAcquireShared()方法小于0时,那么会执行doAcquireShared方法将该线程加入到等待队列中。
Sync实现了tryAcquireShared方法,如下:

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            //如果当前有写线程并且本线程不是写线程,不符合重入,失败
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //得到读锁的个数
            int r = sharedCount(c);
            //如果读不应该阻塞并且读锁的个数小于最大值65535,并且可以成功更新状态值,成功
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                //如果当前读锁为0
                if (r == 0) {
                    //第一个读线程就是当前线程
                    firstReader = current;
                    firstReaderHoldCount = 1;
                }
                //如果当前线程重入了,记录firstReaderHoldCount
                else if (firstReader == current) {
                    firstReaderHoldCount++;
                }
                //当前读线程和第一个读线程不同,记录每一个线程读的次数
                else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            //否则,循环尝试
            return fullTryAcquireShared(current);
        }

从上面的代码以及注释可以看到,分为三步:
1. 如果当前有写线程并且本线程不是写线程,那么失败,返回-1
2. 否则,说明当前没有写线程或者本线程就是写线程(可重入),接下来判断是否应该读线程阻塞并且读锁的个数是否小于最小值,并且CAS成功使读锁+1,成功,返回1。其余的操作主要是用于计数的
3. 如果2中失败了,失败的原因有三,第一是应该读线程应该阻塞;第二是因为读锁达到了上线;第三是因为CAS失败,有其他线程在并发更新state,那么会调动fullTryAcquireShared方法。
fullTryAcquiredShared方法如下:

 final int fullTryAcquireShared(Thread current) {

            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                //一旦有别的线程获得了写锁,返回-1,失败
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                } 
                //如果读线程需要阻塞
                else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    }
                    //说明有别的读线程占有了锁
                    else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                //如果读锁达到了最大值,抛出异常
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //如果成功更改状态,成功返回
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

 从上面可以看到fullTryAcquireShared与tryAcquireShared有很多类似的地方。
在上面可以看到多次调用了readerShouldBlock方法,对于公平锁,只要队列中有线程在等待,那么将会返回true,也就意味着读线程需要阻塞;对于非公平锁,如果当前有线程获取了写锁,则返回true。一旦不阻塞,那么读线程将会有机会获得读锁。

写锁的获取

写锁的lock方法如下:

public void lock() {
            sync.acquire(1);
        }

AQS的acquire方法如下:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

从上面可以看到,写锁使用的是AQS的独占模式。首先尝试获取锁,如果获取失败,那么将会把该线程加入到等待队列中。
Sync实现了tryAcquire方法用于尝试获取一把锁,如下:

protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
             //得到调用lock方法的当前线程
            Thread current = Thread.currentThread();
            int c = getState();
            //得到写锁的个数
            int w = exclusiveCount(c);
            //如果当前有写锁或者读锁
            if (c != 0) {
                // 如果写锁为0或者当前线程不是独占线程(不符合重入),返回false
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //如果写锁的个数超过了最大值,抛出异常
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 写锁重入,返回true
                setState(c + acquires);
                return true;
            }
            //如果当前没有写锁或者读锁,如果写线程应该阻塞或者CAS失败,返回false
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            //否则将当前线程置为获得写锁的线程,返回true
            setExclusiveOwnerThread(current);
            return true;
        }

 从代码和注释可以看到,获取写锁时有三步:
1. 如果当前有写锁或者读锁。如果只有读锁,返回false,因为这时如果可以写,那么读线程得到的数据就有可能错误;如果有写锁,但是线程不同,即不符合写锁重入规则,返回false
2. 如果写锁的数量将会超过最大值65535,抛出异常;否则,写锁重入
3. 如果没有读锁或写锁的话,如果需要阻塞或者CAS失败,返回false;否则将当前线程置为获得写锁的线程

 从上面可以看到调用了writerShouldBlock方法,FairSync的实现是如果等待队列中有等待线程,则返回false,说明公平模式下,只要队列中有线程在等待,那么后来的这个线程也是需要记入队列等待的;NonfairSync中的直接返回的直接是false,说明不需要阻塞。从上面的代码可以得出,当没有锁时,如果使用的非公平模式下的写锁的话,那么返回false,直接通过CAS就可以获得写锁。

总结

从上面分析可以得出结论:
- 如果当前没有写锁或读锁时,第一个获取锁的线程都会成功,无论该锁是写锁还是读锁。
- 如果当前已经有了读锁,那么这时获取写锁将失败,获取读锁有可能成功也有可能失败
- 如果当前已经有了写锁,那么这时获取读锁或写锁,如果线程相同(可重入),那么成功;否则失败

释放锁

获取锁要做的是更改AQS的状态值以及将需要等待的线程放入到队列中;释放锁要做的就是更改AQS的状态值以及唤醒队列中的等待线程来继续获取锁。

读锁的释放

ReadLock的unlock方法如下:

 public void unlock() {
            sync.releaseShared(1);
        }

调用了Sync的releaseShared方法,该方法在AQS中提供,如下:

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

调用tryReleaseShared方法尝试释放锁,如果释放成功,调用doReleaseShared尝试唤醒下一个节点。
AQS的子类需要实现tryReleaseShared方法,Sync中的实现如下:

protected final boolean tryReleaseShared(int unused) {
            //得到调用unlock的线程
            Thread current = Thread.currentThread();
            //如果是第一个获得读锁的线程
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            }
            //否则,是HoldCounter中计数-1
            else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            //死循环
            for (;;) {
                int c = getState();
                //释放一把读锁
                int nextc = c - SHARED_UNIT;
                //如果CAS更新状态成功,返回读锁是否等于0;失败的话,则重试
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }

 从上面可以看到,释放锁的第一步是更新firstReader或HoldCounter的计数,接下来进入死循环,尝试更新AQS的状态,一旦更新成功,则返回;否则,则重试。
释放读锁对读线程没有影响,但是可能会使等待的写线程解除挂起开始运行。所以,一旦没有锁了,就返回true,否则false;返回true后,那么则需要释放等待队列中的线程,这时读线程和写线程都有可能再获得锁。

写锁的释放

WriteLock的unlock方法如下:

 public void unlock() {
            sync.release(1);
        }

Sync的release方法使用的AQS中的,如下:

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

 调用tryRelease尝试释放锁,一旦释放成功了,那么如果等待队列中有线程再等待,那么调用unparkSuccessor将下一个线程解除挂起。
Sync需要实现tryRelease方法,如下:

protected final boolean tryRelease(int releases) {
            //如果没有线程持有写锁,但是仍要释放,抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            //如果没有写锁了,那么将AQS的线程置为null
            if (free)
                setExclusiveOwnerThread(null);
            //更新状态
            setState(nextc);
            return free;
        }

从上面可以看到,写锁的释放主要有三步:
1. 如果当前没有线程持有写锁,但是还要释放写锁,抛出异常
2. 得到解除一把写锁后的状态,如果没有写锁了,那么将AQS的线程置为null
3. 不管第二步中是否需要将AQS的线程置为null,AQS的状态总是要更新的

从上面可以看到,返回true当且只当没有写锁的情况下,还有写锁则返回false。

总结

从上面的分析可以得出:
- 如果当前是写锁被占有了,只有当写锁的数据降为0时才认为释放成功;否则失败。因为只要有写锁,那么除了占有写锁的那个线程,其他线程既不可以获得读锁,也不能获得写锁
- 如果当前是读锁被占有了,那么只有在写锁的个数为0时才认为释放成功。因为一旦有写锁,别的任何线程都不应该再获得读锁了,除了获得写锁的那个线程。

其他方法

看完了ReentrantReadWriteLock中的读锁的获取和释放,写锁的获取和释放,再来看一下其余的一些辅助方法来加深我们对读写锁的理解。
getOwner()

getOwner方法用于返回当前获得写锁的线程,如果没有线程占有写锁,那么返回null。实现如下:

 protected Thread getOwner() {
        return sync.getOwner();
    }

可以看到直接调用了Sync的getOwner方法,下面是Sync的getOwner方法:

final Thread getOwner() {
            // Must read state before owner to ensure memory consistency
            return ((exclusiveCount(getState()) == 0) ?
                    null :
                    getExclusiveOwnerThread());
        }

如果独占锁的个数为0,说明没有线程占有写锁,那么返回null;否则返回占有写锁的线程。

getReadLockCount()

getReadLockCount()方法用于返回读锁的个数,实现如下:

public int getReadLockCount() {
        return sync.getReadLockCount();
    }

Sync的实现如下:

final int getReadLockCount() {
            return sharedCount(getState());
        }

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

从上面代码可以看出,要想得到读锁的个数,就是看AQS的state的高16位。这和前面讲过的一样,高16位表示读锁的个数,低16位表示写锁的个数。

getReadHoldCount()

getReadHoldCount()方法用于返回当前线程所持有的读锁的个数,如果当前线程没有持有读锁,则返回0。直接看Sync的实现即可:

final int getReadHoldCount() {
            //如果没有读锁,自然每个线程都是返回0
            if (getReadLockCount() == 0)
                return 0;

            //得到当前线程
            Thread current = Thread.currentThread();
            //如果当前线程是第一个读线程,返回firstReaderHoldCount参数
            if (firstReader == current)
                return firstReaderHoldCount;
            //如果当前线程不是第一个读线程,得到HoldCounter,返回其中的count
            HoldCounter rh = cachedHoldCounter;
            //如果缓存的HoldCounter不为null并且是当前线程的HoldCounter,直接返回count
            if (rh != null && rh.tid == getThreadId(current))
                return rh.count;

            //如果缓存的HoldCounter不是当前线程的HoldCounter,那么从ThreadLocal中得到本线程的HoldCounter,返回计数         
            int count = readHolds.get().count;
            //如果本线程持有的读锁为0,从ThreadLocal中移除
            if (count == 0) readHolds.remove();
            return count;
        }

从上面的代码中,可以看到两个熟悉的变量,firstReader和HoldCounter类型。这两个变量在读锁的获取中接触过,前面没有细说,这里细说一下。HoldCounter类的实现如下:

 static final class HoldCounter {
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());
        }

readHolds是ThreadLocalHoldCounter类,定义如下:

 static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

可以看到,readHolds存储了每一个线程的HoldCounter,而HoldCounter中的count变量就是用来记录线程获得的写锁的个数。所以可以得出结论:Sync维持总的读锁的个数,在state的高16位;由于读线程可以同时存在,所以每个线程还保存了获得的读锁的个数,这个是通过HoldCounter来保存的。
除此之外,对于第一个读线程有特殊的处理,Sync中有如下两个变量:

private transient Thread firstReader = null;
        private transient int firstReaderHoldCount;

firstReader表示第一个得到读锁的线程,firstReaderHoldCount表示这个线程获得的写锁。所以可以得出结论:第一个获取到读锁的信息保存在firstReader中;其余获取到读锁的线程的信息保存在HoldCounter中。
看完了HoldCounter和firstReader,再来看一下getReadLockCount的实现,主要有三步:
1. 当前没有读锁,那么自然每一个线程获得的读锁都是0;
1. 如果当前线程是第一个获取到读锁的线程,那么返回firstReadHoldCount;
1. 如果当前线程不是第一个获取到读锁的线程,得到该线程的HoldCounter,然后返回其count字段。如果count字段为0,说明该线程没有占有读锁,那么从readHolds中移除。获取HoldCounter分为两步,第一步是与cachedHoldCounter比较,如果不是,则从readHolds中获取。

getWriteLockCount()

getWriteLockCount()方法返回写锁的个数,Sync的实现如下:

final int getWriteHoldCount() {
            return isHeldExclusively() ? exclusiveCount(getState()) : 0;
        }

可以看到如果没有线程持有写锁,那么返回0;否则返回AQS的state的低16位。

总结

 JDK中Concurrent包中关于各种锁的实现基石来源于AQS,因此在解读各种锁的源码或者使用锁之前,需要仔细阅读AQS中各种模式下锁获取与释放的流程,只有完全理解了AQS中各种模式下锁的实现原理,对于分析ReentrantLock与ReentrantReadWriteLock也就事半功倍了。

猜你喜欢

转载自blog.csdn.net/Pengjx2014/article/details/78940315