ReentrantReadWriteLock——总结(读写锁的实现)

ReentrantReadWriteLock类的大体结构

在看完 ReentrantReadWriteLock 的源码后,发现主要还是通过它的内部类 Sync(当然咯,还有其它的内部类) 实现的,而 Sync 又继承了 AbstractQueuedSynchronizer 类;同时还通过 ThreadLocalHoldCounter(此类继承了ThreadLocal)来记录HoldCounter 对象。所以,在阅读源码时,建议最好掌握以下知识点,不然可能会有点儿懵:

知识
AQS 的实现
ThreadLocal 的实现

首先先创建一个 ReentrantReadWriteLock 对象:

ReadWriteLock lock = new ReentrantReadWriteLock(false);

重点在 sync 会根据传入的 fair(公平) 值来确定,这里传入为 false ,所以 " sync = new FairSync()"

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

然后再看读锁的实现 :

读锁的实现

加锁和释放锁操作为:

lock.readLock().lock();
lock.readLock().unlock();

具体实现:

  1. 外部调用 lock 方法
 public void lock() {
      sync.acquireShared(1);
 }
  1. 调用内部类 Sync 的 acquireShared 方法,且传入参数 为 1;
public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
  1. 在第2步中判断 tryAcquireShared(arg) < 0 ,其中参数 arg 为 1,执行 tryAcquireShared 方法
    第一次上读锁时,获取成功,随后更新对应的状态记录。但如果返回 -1 呢(即已经被其它线程上
    了写锁)?流程看3.1.x ;如果返回的是 fullTryAcquireShared(current) , 流程看 3.2.x ;
// 位置 ReentrantReadWriteLock 源码 448 行
 protected final int tryAcquireShared(int unused) { //此时 unused 的值为 1
            /*
 			 * 1. 如果另一个线程持有写锁,则失败
 			 * 2. 除此之外,该线程符合 wrt 状态,因此需要询问(ask)由于队列策略是否应该阻塞
 			 * 如果不阻塞,尝试 CASing 状态授予许可并更新计数(count).注意:该步骤步检查重入
 			 * 获取,推迟到完整版本(这里应该指的是子类),已避免在更典型的非重入的情况下必须
 			 * 检查计数(count)
 			 * 3. 如果第二步失败,因为线程不符合条件或 CAS 失败或计数(count)饱和,链接到完
 			 * 整版本重试
             */
            Thread current = Thread.currentThread(); // 获取当前线程
            int c = getState(); // 获取状态值
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1; // 写锁计数不为0,并且上写锁线程不是当前线程(就是被其它线程上了写锁),直接返回 -1
            int r = sharedCount(c); // 获取 读锁数量
            if (!readerShouldBlock() &&   
                r < MAX_COUNT && 
                compareAndSetState(c, c + SHARED_UNIT)) { // 当前线程获取读锁,状态变更成功(上锁成功)
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } 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);
        }

3.1.1 此时 tryAcquireShared 返回的是 -1,即已被其它线程上了写锁,则在 2 中满足判断,
进入if代码块,执行代码: doAcquireShared(arg); 参数 arg 为 1

   /**
     * Acquires in shared uninterruptible mode
     */
     // 代码位置:AQS 948行
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);    //添加到同步队列中
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {  // 死循环
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);  // 再次执行 tryAcquireShared 方法
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrxunhupted = true; 
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

3.1.2 若执行的是 fullTryAcquireShared 方法

  		/**
         * Full version of acquire for reads, that handles CAS misses
         * and reentrant reads not dealt with in tryAcquireShared.
         * 完整版本的获取读取,可处理 tryAcquireShared 中未处理的 CAS 
         * 丢失和重入读取
         */
        final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {  // 死循环
                int c = getState(); // 获取状态值 
                if (exclusiveCount(c) != 0) { 
                    if (getExclusiveOwnerThread() != current)
                        return -1; // 写锁不为0,并且所属不是当前线程,即被其它线程加了写锁,直接返回-1
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                } 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;  // 返回 -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;
                }
            }
        }

所以,读锁的是:
**注:① 这里的 “小于0入队” 和 “大于0加锁成功” 描述不是很精确,因为在入队后仍然是死循环,其中仍不断进行尝试执行 tryAcquireShared 方法 ② 整体执行流程是这样的,加锁 和 入队后的代码具体实现需要自己去看了,这里再画的话图会非常小 **

链接
小于0
大于0
创建ReentrantReadWriteLock
判断是否采用公平策略
创建FairSync
创建NonfairSync
调用 readLock 方法
调用 lock 方法
调用 acquireShared 方法
对 tryAcquireShared 返回值进行判断
入队
加锁成功

上面这个细了,感觉图会有点儿小,省一步看看图能否会大一些:

初始化时
小于0
大于0
ReentrantReadWriteLock
判断是否采用公平策略
创建FairSync
创建NonfairSync
调用 readLock 的 lock
调用 acquireShared 方法
对 tryAcquireShared 返回值判断
入队
加锁成功

写锁的实现

	readWriteLock.writeLock().lock(); // 加锁
    readWriteLock.writeLock().lock(); // 释放锁
  1. 首先仍是 外部调用 lock() 方法
 	public void lock() {
            sync.acquire(1);
        }
  1. 然后调用 acquire 方法
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&  // 步骤 ①
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 步骤 ②
            selfInterrupt();
    }
  1. 执行步骤 ① 方法 tryAcquire()
    protected final boolean tryAcquire(int acquires) {
         /**
         * 1. 如果读计数为非零或写入计数为非零,并且所有者线程是另一个线程,则失败(代表已上锁)
         * 2. 如果计数饱和,则失败(只有在count已经不为0 的情况下才可以发生)
         * 3. 除此之外,如果该线程是允许可重入获取策略的话,则有资格进行上锁。如果是这样,请更
         * 新状态并设置所有者
         */
            Thread current = Thread.currentThread(); // 获取当前线程
            int c = getState();  // 获取状态值
            int w = exclusiveCount(c); // 获取写锁的计数
            if (c != 0) {  // c != 0 为true的话,代表已上锁,但上的是读锁还是写锁无法确定
                // (Note: if c != 0 and w == 0 then shared count != 0)
                // 注意这里的条件是 || ,也就是说
                // 如果: w==0为true即代表了上的是读锁,后面的条件不会再进行判断
                // 如果: w==0为false,即代表已经上了写锁,若 current!=getExclusiveOwnerThread()为true代表
                // 非重入,即此时被其它线程加了写锁,直接返回 
                if (w == 0 || current != getExclusiveOwnerThread()) 
                    return false; //这里一个问题,如果是自身线程上的读锁也会执行返回?见后面的实验
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires); // 重入情况,不许通过CAS,因为已经上锁了
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires)) // 非重入情况,需要通过CAS来进行设置
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

  1. 如果 tryAcquire 返回 false 的话,说明加锁失败了,执行步骤②的 acquireQueued()方法,代码如下,再死循环中
    仍不断去执行 tryAcquire() 方法
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) { //死循环
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) { // 仍不断执行 tryAcquire 方法
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

实验:在上面的源码中,分析看到,若 A 线程加了读锁后(不释放读锁),A线程再加写锁会直接返回false,然后进入acquireQueued方法中,在死循环中不断尝试,真的是这样的嘛?

实验代码如下:

	@Test 
	public void ReentrantReadWriteLockTest2() throws Exception{
		
		ReadWriteLock lock = new ReentrantReadWriteLock(false); // 创建 对象
		lock.readLock().lock(); // 先加读锁
		System.out.println("加读锁 ....."); // 为了能看到结果
		lock.writeLock().lock();
		System.out.println("加写锁.....");	
		lock.readLock().lock(); //释放读锁
		lock.writeLock().lock();  // 释放写锁 
	}

实验结果如下:
在这里插入图片描述
实验结果分析:
**实验结果和在代码分析中的一样,当加了读锁后(无论是当前线程还是其它线程加的读锁),是无法再加写锁的,加写锁会不断进行尝试,直到释放了读锁,加写锁才能成功。当然咯,这个实验是不完整的,完整的实验还要加上 “加读锁后立即释放读锁,再加写锁,释放写锁”但这个实验我觉得就没有必要浪费笔墨了 **

这里就不对公平与不公平进行分析了,因为我之前的 ReentrantLock 中已经分析过了,虽然这里代码上会有些不一样,但都只是一个壳子,基本上都是换汤不换药的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值