ReentrantReadWriteLock(读写锁)

由于ReentrantLook锁是独占锁,同时只允许一个线程进行访问,那么如果存在如下场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作。

针对这种场景,java中提供了ReentrantReadWriteLock来实现,它包含了两个锁实现,一个ReadLock是共享锁,WriteLock是独立锁。下面分析一下读写锁的实现思路。

使用场景

读写锁的使用demo中,doug Lea给出了两种场景,第一种是CacheData典型的多读少写的场景,第二种是RWDictionary 。

class RWDictionary {

     private final Map<String, Data> m = new TreeMap<String, Data>();

     private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

     private final Lock r = rwl.readLock();

     private final Lock w = rwl.writeLock();

     public Data get(String key) {

       r.lock();

       try {

         return m.get(key);

       } finally {

         r.unlock();

       }

     }

     public String[] allKeys() {

       r.lock();

       try {

         return m.keySet().toArray();

       }

       finally {

         r.unlock();

       }

     }

     public Data put(String key, Data value) {

       w.lock();

       try {

         return m.put(key, value);

       } finally {

         w.unlock();

       }

     }

     public void clear() {

       w.lock();

       try {

         m.clear();

       } finally {

         w.unlock();

       }

     }

}

读写锁定义

在读写锁中,同样分为公平锁与非公平锁,默认使用非公平锁.

//ReentrantReadWriteLock的数据结构定义

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {

    /** 读锁*/

    private final ReentrantReadWriteLock.ReadLock readerLock;

    /** 写锁*/

    private final ReentrantReadWriteLock.WriteLock writerLock;

    final Sync sync;

  //非公平锁实现

    static final class NonfairSync extends Sync {

    final boolean writerShouldBlock() {

        return false; // writers can always barge

    }

    final boolean readerShouldBlock() {

        return apparentlyFirstQueuedIsExclusive();

    }

  }

  //公平锁实现

  static final class FairSync extends Sync {

    final boolean writerShouldBlock() {

        return hasQueuedPredecessors();

    }

    final boolean readerShouldBlock() {

        return hasQueuedPredecessors();

    }

  }

  //读写锁默认为非公平锁

  public ReentrantReadWriteLock() {

    this(false);

  }

  //锁实现的基类,读锁与写锁分别实现些基类

  //读锁与写锁最大可以各存储65535个锁.

  abstract static class Sync extends AbstractQueuedSynchronizer {

    static final int SHARED_SHIFT   = 16;

    //高16位存储读锁(共享锁)的数量

    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);

    //单个锁最大可用数据65535.

    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;

    //低16位存储写锁数量的掩码

    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

  }

}

WriteLock实现(独占锁)

  1. WriteLock.lock入口

WriteLock加锁部分任然是直接调用AQS.acquire来获取锁。

//ReentrantReadWriteLock中的独占锁WriteLock实现加锁的流程

public static class WriteLock implements Lock, Serializable {

  //这里直接调用了对应公平锁或者非公平锁的acquire来进行加锁.

  public void lock() {

    //调用AQS的acquire函数,此函数会执行如下几个流程:

    //=>1,"tryAcquire"对当前线程进行加锁,如果加锁成功,直接结束流程,否则执行步骤2.

    //=>2,"addWaiter"线程加锁失败,把线程添加到等待队列中(队列尾部),并执行步骤3.

    //=>3,"acquireQueued"线程不断尝试抢占锁资源,如果抢占锁成功结束流程,否则执行步骤4.

    //=>4,"selfInterrupt"线程处于BLOCKED状态,

    //===>同时被标记中断(读取中断标记后标记位会被清理),重新把线程标记为中断状态.

    sync.acquire(1);

  }

}

2.tryAcquire函数实现

线程进入WriteLock锁的条件:

1,当前锁资源没有被任何线程持有(即没有读锁也没有写锁)

2,如果锁资源已经被线程持有,这个持有者线程必须是当前线程本身(不能是共享锁).

3,WriteLock锁的重入次数最大不能超过65535次。

当线程抢占锁资源失败,返回false,由AQS添加到等待队列等待唤醒,并重复执行此步骤。

//ReentrantReadWriteLock.Sync.tryAcquire实现锁资源抢占实现

protected final boolean tryAcquire(int acquires) {

    //先获取锁的状态位标记属性state.

    Thread current = Thread.currentThread();

    int c = getState();

    //计算当前写锁的总锁数量(c变量的低16位)"c & EXCLUSIVE_MASK"

    int w = exclusiveCount(c);

    if (c != 0) {

        //"c != 0 && w == 0"表示没有WriteLock锁,只有ReadLock的共享锁.

        //==>锁的线程持有者不是当前线程,表示抢占锁资源失败,返回false.

        // (Note: if c != 0 and w == 0 then shared count != 0)

        if (w == 0 || current != getExclusiveOwnerThread())

            return false;

        //流程执行到这里,表示锁的持有者就是当前线程本身.

        //==>判断WriteLock的锁数量是否大于锁的最大记录数(65535),不能超过65535个WriteLock锁。 

        if (w + exclusiveCount(acquires) > MAX_COUNT)

            throw new Error("Maximum lock count exceeded");

        // Reentrant acquire

        //线程重入锁(当前线程就是持有锁的线程),并记录WriteLock的数量加1.

        setState(c + acquires);

        return true;

    }

    //没有任何线程持有锁(state==0),这里分为两种情况:

    //1,非公平锁:执行"compareAndSetState(c, c + acquires)",

    //==>直接对锁数量的状态位进行CAS操作,失败表示抢占锁失败,直接返回false结束流程.

    //2,公平锁:先执行"writerShouldBlock()",

    //==>判断等待队列的中当前线程的前面是否还有等待的线程,

    //====>如果有返回true表示抢占锁失败,直接返回false结束流程.

    //====>如果等待队列为空,或者等待队列的队顶元素就是当前线程,说明可以重入,执行下一歩操作:

    //==>执行"compareAndSetState(c, c + acquires)",

    //====>直接对锁数量的状态位进行CAS操作,失败表示抢占锁失败,直接返回false结束流程.

    //====>这种情况只有一种场景会失败,等待队列为空,同一时间两个线程来抢占锁资源.

    if (writerShouldBlock() ||!compareAndSetState(c, c + acquires)) {

        return false;

    }

    //当前线程抢占锁成功,设置当前线程为WriteLock的所有者,并返回true.

    setExclusiveOwnerThread(current);

    return true;

}

//公平锁的writerShouldBlock函数,判断是否需要阻止线程获取写锁.

//注意:非公平锁默认不阻止线程获取写锁,即:函数在非公平锁时直接返回false.

final boolean writerShouldBlock() {

//直接调用AQS的hasQueuedPredecessors函数。

//这里判断队列是否为空或者队列的队顶元素是否是当前线程,是当前线程返回false.

//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.

    return hasQueuedPredecessors();

}

//AQS.hasQueuedPredecessors,判断队顶元素是否是当前线程,如果不是当前线程返回true.

//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.

public final boolean hasQueuedPredecessors() {

    // The correctness of this depends on head being initialized

    // before tail and on head.next being accurate if the current

    // thread is first in queue.

    Node t = tail; // Read fields in reverse initialization order

    Node h = head;

    Node s;

    return h != t &&

        ((s = h.next) == null || s.thread != Thread.currentThread());

}

3.WriteLock释放锁

WriteLock锁是独占锁,因此释放锁的入口是直接调用AQS.release来进行释放,而AQS.release依赖锁具体实现的tryRelease函数来释放锁资源。

//WriteLock.unlock释放锁资源与独占锁的释放相同,直接走AQS.release.

public void unlock() {

    //AQS.release依赖当前Lock接口中的tryRelease函数.

    sync.release(1);

}

//Sync.tryRelease函数,完成当前线程锁的释放,

//==>返回true表示当前线程持有的锁资源已经完全释放.

//==>返回false表示线程有重入锁还未释放完.

protected final boolean tryRelease(int releases) {

    //1,先判断当前线程是否是锁的owner,必须是当前锁持有者才能释放锁.

    if (!isHeldExclusively())

        throw new IllegalMonitorStateException();

    //对锁持有数量减1(直接减是减低16位),即:对WriteLock的持有数减1.

    int nextc = getState() - releases;

//判断写锁的持有数量(低16位)是否为0,"c & EXCLUSIVE_MASK;"

//注意:这里只判断低16位,不判断高16位(即读锁不管)

    //锁持有数量为0表示锁释放完成,否则表示有重入锁还未完全释放返回false.

boolean free = exclusiveCount(nextc) == 0;

//如果锁释放完成,清空独占锁的线程owner

    if (free)

        setExclusiveOwnerThread(null);

    setState(nextc);

    return free;

}

ReadLock实现(共享锁)

1.ReadLock.lock入口

//ReadLock.lock加锁流程,这里是共享锁,因此调用流程与独占锁不同

public void lock() {

    //直接调用AQS.acquireShared来获取共享锁资源.

    sync.acquireShared(1);

}

2,tryAcquireShared

此函数用于获取共享锁资源,由AQS负责调用。由ReentrantReadWriteLock.Sync进行实现。

在共享锁的实现中包含了如下几个变量:

firstReader/firstReaderHoldCount : 首次获取到共享锁的线程与锁计数器.

cachedHoldCounter : 最后一次获取共享锁的计数器实例(HoldCounter).

==>这个计数器中存储了最后一个获取锁的线程ID与锁计数,在超过一个线程获取共享锁时,时变量存在。

readHolds : 继承与ThreadLocal的ThreadLocalHoldCounter实例,用于维护每个持有共享锁的线程计数器.

这个函数主要完成如下几个场景:

1,资源有被写锁持有,但持有写锁的线程不是当前线程,返回-1,表示获取读锁失败,需要添加到等待队列中交由AQS的doAcquireShared来处理。

2,“readerShouldBlock()”不阻止线程获取读取锁,同时读取锁数据小于65535,同时CAS设置读取锁计数器成功(在原值上加1),表示获取读取锁成功,根据holdCount计数器.

3,其它情况调用”fullTryAcquireShared”,CAS自旋不断尝试获取锁资源。这里有一个锁降级的概率,其实就是当前线程如果持有写锁,那么线程要获取共享锁时可以直接重入,减少一次释放写锁在重新抢占读锁的过程

//Sync.tryAcquireShared尝试获取共享锁资源,

protected final int tryAcquireShared(int unused) {

    Thread current = Thread.currentThread();

    int c = getState();

    //1,如果有线程持有写锁资源,同时写锁的持有者非当前线程,直接结束流程返回-1.

    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)

        return -1;

    //2,获取共享锁的数量“c >>> SHARED_SHIFT;”,计算高16位的值.

    int r = sharedCount(c);

    //=>a,"!readerShouldBlock()"判断是否应该阻止线程获取读锁(共享锁),

    //====>返回false表示不阻止

    //=>b,"r < MAX_COUNT"如果共享锁的数据不超过65535个,表示可以继续申请锁资源.

    //=>c,"compareAndSetState(c, c + SHARED_UNIT)",CAS设置state高16位加1,

    //====>设置成功表示当前线程抢占到资源

    if (!readerShouldBlock() &&

             r < MAX_COUNT &&

             compareAndSetState(c, c + SHARED_UNIT)) {

        //流程执行到这里,说明当前线程成功获取到读取锁.

        if (r == 0) {

            //"r == 0",当前没有线程持有读锁,说明当前线程是第一个读取锁的持有者.

            firstReader = current;

            firstReaderHoldCount = 1;

        } else if (firstReader == current) {

            //第一个读取锁持有者就是当前线程,读取锁计数器加1.

            firstReaderHoldCount++;

        } else {

            //“cachedHoldCounter”上一次获取到读取锁的线程计数器.

            HoldCounter rh = cachedHoldCounter;

            //"rh == null",首次非firstReader线程进入.

            if (rh == null || rh.tid != getThreadId(current)) {

                //获取当前线程对应的"HoldCounter"实例

                //==>这是一个继承ThreadLocal的的类,"readHolds.get()"请参考ThreadLocal.get实现

                 //==>readHolds是“ThreadLocalHoldCounter”的实例继承与ThreadLocal

                //==>如果当前线程没有初始化,会调用"initialValue"生成一个新的线程计数器.

                cachedHoldCounter = rh = readHolds.get();

            } else if (rh.count == 0) {

                //此时“rh != null”,同时rh.count计数器是0,用上个线程的HoldCounter覆盖当前线程.

                readHolds.set(rh);

            }

            //线程读取锁计数器加1.

            rh.count++;

        }

        //成功获取到读取锁,返回1结束流程.  

        return 1;

    }

    //3,最后一种情况,CAS自旋不断尝试获得读取锁,

    //==>如果写锁的持有者是当前线程本身时会进行锁降级,(即重入锁然后释放写锁后就只剩下读取锁)

    return fullTryAcquireShared(current);

}

3,readerShouldBlock

此函数的作用是判断当前线程是否阻止获取共享锁,返回false表示可以获取共享锁。

函数的实现分为非公平锁与公平锁。

非公平锁的实现

非公平锁模式下,如果队顶元素是独占锁时,阻止当前线程获取共享锁.

//NonfairSync.readerShouldBlock非公平锁判断是否需要阻止获取共享锁,

final boolean readerShouldBlock() {

    //调用AQS中的函数来判断队顶元素是否是独占锁线程,如果是返回true.

    return apparentlyFirstQueuedIsExclusive();

}

//AQS.apparentlyFirstQueuedIsExclusive判断队顶元素是否是独占锁,如果是返回true.

final boolean apparentlyFirstQueuedIsExclusive() {

    Node h, s;

    return (h = head) != null &&

        (s = h.next)  != null &&

        !s.isShared()         &&

        s.thread != null;

}

公平锁的实现

公平锁模式下,判断等待队列中当前线程是否还有前继节点,如果有返回true,否则返回false.

//FairSync.readerShouldBlock公平锁判断是否需要阻止获取共享锁,

final boolean readerShouldBlock() {

    //调用AQS.hasQueuedPredecessors判断队顶元素是否是当前线程,

    //==>如果不是当前线程返回true,表示阻止线程获取共享锁

    //==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.

    return hasQueuedPredecessors();

}

//AQS.hasQueuedPredecessors,判断队顶元素是否是当前线程,如果不是当前线程返回true.

//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.

public final boolean hasQueuedPredecessors() {

    // The correctness of this depends on head being initialized

    // before tail and on head.next being accurate if the current

    // thread is first in queue.

    Node t = tail; // Read fields in reverse initialization order

    Node h = head;

    Node s;

    return h != t &&

        ((s = h.next) == null || s.thread != Thread.currentThread());

}

4,fullTryAcquireShared

CAS自旋尝试获取共享锁,几种情况下可以获取到共享锁:

  1. 写锁持有线程是当前线程。
  2. 写锁是空闲状态,即使是有其它线程持有共享锁,也可以获取到共享锁。
  3. 其它情况失败,返回-1,交由AQS的doAcquireShared来处理。

//Sync.fullTryAcquireShared,CAS自旋锁不断尝试获取共享锁.

final int fullTryAcquireShared(Thread current) {

       //CAS自旋,不断迭代.

    HoldCounter rh = null;

    for (;;) {

        int c = getState();

        //如果写锁持有者不是当前线程,表达获取锁失败,直接返回-1.

        //相反写锁持有者就是当前线程,继续执行后面的流程.

        if (exclusiveCount(c) != 0) {

            if (getExclusiveOwnerThread() != current)

                return -1;

            // else we hold the exclusive lock; blocking here would cause deadlock.

        //时此"exclusiveCount(c) == 0",这时表示写锁是空闲状态(没有线程持有写锁)

        } else if (readerShouldBlock()) {

            // Make sure we're not acquiring read lock reentrantly

            //第一个共享锁的持有者就是当前线程.

            if (firstReader == current) {

                // assert firstReaderHoldCount > 0;

            } else {

                //第一个共享锁持有者不是当前线程,

                if (rh == null) {

                    //刷新cachedHoldCounter(最后一个共享锁的持有者)

                    rh = cachedHoldCounter; 

                    //如果最后一个共享锁的持有线程不是当前线程,得到当前线程的HoldCounter计数器

                    if (rh == null || rh.tid != getThreadId(current)) {

                        rh = readHolds.get();

                        //当前线程的HoldCounter计数器为0,表示线程没有持有任何共享锁,

                        //==>从ThreadLocalMap中移出此计数器

                        if (rh.count == 0)

                            readHolds.remove();

                    }

                }

                //当前线程的HoldCounter计数器为0,直接返回-1,让AQS把线程添加到等待队列中.

                if (rh.count == 0)

                    return -1;

            }

        }

        //共享锁的持有数量超过最大值.

        if (sharedCount(c) == MAX_COUNT)

            throw new Error("Maximum lock count exceeded");

        //CAS尝试设置sharedState状态值,即:把共享锁数量加1,如果成功表示获取锁成功

        //==>1,此时当前线程可能本身持有写锁,重入共享锁(锁降级) 

         //==>2,资源写锁空闲,即使是有其它线程持有共享锁(readerShouldBlock()==true),也可以获取锁.

        if (compareAndSetState(c, c + SHARED_UNIT)) {

            //如果当前线程是第一个获取共享锁的线程,设置firstReader为当前线程.

            //==>对firstReaderHoldCount计数器加1.

            if (sharedCount(c) == 0) {

                firstReader = current;

                firstReaderHoldCount = 1;

            } else if (firstReader == current) {

                //当前线程是首次获取共享锁的线程,firstReaderHoldCount计数器加1.

                firstReaderHoldCount++;

            } else {

                //更新最后一个共享锁持有者,"cachedHoldCounter"计数器缓存.

                if (rh == null)

                    rh = cachedHoldCounter;

                if (rh == null || rh.tid != getThreadId(current))

                    rh = readHolds.get();

                else if (rh.count == 0)

                    readHolds.set(rh);

                //HoldCounter计数器加1.

                rh.count++;

                cachedHoldCounter = rh; // cache for release

            }

            return 1;

        }

    }

}

5,锁的释放

ReadLock中锁的释放同样直接调用“AQS.releaseShared”的释放锁来进行处理。

请参考“AQSreleaseShared”实现。

//ReentrantReadWriteLock.ReadLock的锁释放流程

public void unlock() {

    //直接调用AQS.releaseShared释放共享锁.

    //1,调用Sync具体实现中的tryReleaseShared释放锁.

    //2,如果锁释放成功,调用doReleaseShared唤醒后继节点.

    sync.releaseShared(1);

}

tryReleaseShared函数:

此函数完成共享锁的释放流程.

//Sync.tryReleaseShared释放共享锁资源的具体实现

protected final boolean tryReleaseShared(int unused) {

    Thread current = Thread.currentThread();

    if (firstReader == current) {

        //如果当前线程是第一个持有共享锁资源的线程,分为两种情况:

        //1,线程只持有一个共享锁,直接设置firstReader为null.

        //2,线程持有多个共享锁句柄,firstReaderHoldCount对应减1.

        // assert firstReaderHoldCount > 0;

        if (firstReaderHoldCount == 1)

            firstReader = null;

        else

            firstReaderHoldCount--;

    } else {

        //1,先判断当前线程是否是最后一个获取共享锁的线程,

        //==>如果不是,从readHolds的ThreadLocalMap中获取当前线程对应的HoldCounter.

        //2,判断线程持有共享锁的句柄个数,如果只有一个,直接从readHolds移出此HoldCounter.

        //==>如果线程持有多个共享锁的句柄,对应HoldCounter.count减1.

        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;

    }

    CAS自旋锁,修改state值高16位共享锁的数量,把共享锁的数量减1.

    for (;;) {

        int c = getState();

        int nextc = c - SHARED_UNIT;

        //当锁资源完全释放完成后(没有读取锁也没有写锁),此函数返回true,否则返回false.

        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;

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值