java多线程之ReentrantReadWriteLock共享锁源码解析

前言

前面我们分析了Synchronized(同步锁),ReentrantLock(独占锁),本篇开始分析ReentrantReadWriteLock(共享锁),JUC中的共享锁还有CountDownLatch, CyclicBarrier, Semaphore,后面都会进行分析。

1、ReentrantReadWriteLock结构图

在这里插入图片描述

2、调用的方法关系图

在这里插入图片描述

3、获取共享锁

  1. ReadLock中的lock方法,源码如下:
public void lock() {
	//Sync继承AQS,此方法实现在AQS中
     sync.acquireShared(1);
}
  1. AQS中的acquireShared方法,源码如下
public final void acquireShared(int arg) {
	//先尝试获取锁,获取成功则返回,失败则执行doAcquireShared方法再去获取锁
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
}
  1. tryAcquireShared()定义在ReentrantReadWriteLock.java的Sync中,源码如下:
protected final int tryAcquireShared(int unused) {
           
	Thread current = Thread.currentThread();
	// 在读写锁模式下,高16位存储的是共享锁(读锁)被获取的次数,低16位存储的是互斥锁(写锁)被获取的次数
	int c = getState();
	//如果独占锁(写锁)已经被获取并且获取独占锁的线程不是当前线程的话,则返回-1
	//如果是独占锁是当前线程获取,则当前线程也可以获取读锁,锁降级
	if (exclusiveCount(c) != 0 &&
		getExclusiveOwnerThread() != current)
		return -1;
	//获取“读取锁”的共享计数
	int r = sharedCount(c);
	// 如果不需要阻塞等待,并且“读取锁”的共享计数小于MAX_COUNT;
    // 则通过CAS函数更新“读取锁”的共享计数+1
	if (!readerShouldBlock() &&
		r < MAX_COUNT &&
		compareAndSetState(c, c + SHARED_UNIT)) {
		//第一次获取读取锁
		if (r == 0) {
			firstReader = current;
			firstReaderHoldCount = 1;
		//如果当前线程是第1个获取锁的线程
		} else if (firstReader == current) {
			firstReaderHoldCount++;
		} else {
			// HoldCounter统计的是当前线程获取“读取锁”的次数
			//下面这几行,就是将 cachedHoldCounter 设置为当前线程
			HoldCounter rh = cachedHoldCounter;
			if (rh == null || rh.tid != getThreadId(current))
				// cachedHoldCounter 是否缓存的是当前线程,不是的话要到 ThreadLocal 中取
				cachedHoldCounter = rh = readHolds.get();
			else if (rh.count == 0)
				readHolds.set(rh);
			rh.count++;
		}
		return 1;
	}
	//if条件失败,则进入这个方法
	return fullTryAcquireShared(current);
}
  1. fullTryAcquireShared()在ReentrantReadWriteLock.java的Sync中,源码如下:
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        // 如果“锁”被“写锁”持有,并且获取锁的线程不是current线程;则返回-1。
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        // 需要阻塞等待
        } else if (readerShouldBlock()) {
        	//进入这里,说明写锁被释放,读锁被阻塞
        	//那么逻辑就很清楚了,这里是处理读锁重入的
            if (firstReader == current) {
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != current.getId()) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                // 如果当前线程获取锁的计数=0,则返回-1,去排队。
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 将线程获取“读取锁”的次数+1。
        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 != current.getId())
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                // 更新线程的获取“读取锁”的共享计数
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}
  1. doAcquireShared()定义在AQS函数中,源码如下:
private void doAcquireShared(int arg) {
    // addWaiter(Node.SHARED)方法前面分析过,作用是:
	//创建“当前线程”对应的节点,并将该线程添加到CLH队列中	
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取当前节点上一个节点
            final Node p = node.predecessor();
            // 如果p是头节点,则尝试获取共享锁。
            if (p == head) {
				//此处是上面分析过的tryAcquireShared方法,这里可以看出:
				//doAcquireShared方法其实就是在循环的调用tryAcquireShared来尝试获取共享锁
                int r = tryAcquireShared(arg);
                //如果成功
                if (r >= 0) {
	                // 头节点后移并传播
					// 传播即唤醒后面连续的读节点
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //此处判断是否进行阻塞等待,若阻塞等待过程中,线程被中断过,则设置interrupted为true。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

4、释放共享锁

  1. ReadLock中的unLock方法,源码如下:
public  void unlock() {
	//Sync继承AQS,此方法实现在AQS中
    sync.releaseShared(1);
}
  1. releaseShared()在AQS中实现,源码如下:
public final boolean releaseShared(int arg) {
	//这里的思路和获取锁一样:
	//通过tryReleaseShared()去尝试释放共享锁,成功直接返回
	//失败,则通过doReleaseShared释放共享锁
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
  1. tryReleaseShared()定义在ReentrantReadWriteLock.java的Sync中,源码如下:
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 如果当前线程是第1个获取锁的线程
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
		//如果等于 1,那么这次释放锁后就不再持有锁了,把 firstReader 置为 null,给后来的线程用
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
		// 判断 cachedHoldCounter 是否缓存的是当前线程,不是的话要到 ThreadLocal 中取
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
			// 这一步将 ThreadLocal remove 掉,防止内存泄漏。因为已经不再持有读锁了
            readHolds.remove();
            if (count <= 0)
				//这里的情况是,lock() 一次,unlock() 好几次才会触发
                throw unmatchedUnlockException();
        }
		//当前线程获取“读取锁”的次数-1
        --rh.count;
    }
    for (;;) {
        // 获取锁的状态
        int c = getState();
        // 将锁的获取次数-1。
        int nextc = c - SHARED_UNIT;
        // 通过CAS更新锁的状态。
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
  1. doReleaseShared()定义在AQS中,源码如下:
private void doReleaseShared() {
    for (;;) {
        // 获取CLH队列的头节点
        Node h = head;
        // 如果头节点不为null,并且头节点不是队列最后一个节点
        if (h != null && h != tail) {
            // 获取头节点对应的线程的状态
            int ws = h.waitStatus;
            // 判断头节点是否是SIGNAL状态
            //如果是,下一个节点所对应的线程需要被唤醒。
            if (ws == Node.SIGNAL) {
                // 设置“头节点对应的线程状态”为0。失败的话,则继续循环。
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                // 唤醒“头节点的下一个节点所对应的线程”。
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 如果头节点发生变化,则继续循环。否则,退出循环。
        if (h == head)                   // loop if head changed
            break;
    }
}

5、实例

class CachedData {
    Object data;
    volatile boolean cacheValid;
    // 读写锁实例
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        // 获取读锁
        rwl.readLock().lock();
        if (!cacheValid) { // 如果缓存过期了,或者为 null
            // 释放掉读锁,然后获取写锁 (后面会看到,没释放掉读锁就获取写锁,会发生死锁情况)
            rwl.readLock().unlock();
            rwl.writeLock().lock();

            try {
                if (!cacheValid) { // 重新判断,因为在等待写锁的过程中,可能前面有其他写线程执行过了
                    data = ...
                    cacheValid = true;
                }
                // 获取读锁 (持有写锁的情况下,是允许获取读锁的,称为 “锁降级”,反之不行。)
                rwl.readLock().lock();
            } finally {
                // 释放写锁,此时还剩一个读锁
                rwl.writeLock().unlock(); // Unlock write, still hold read
            }
        }

        try {
            use(data);
        } finally {
            // 释放读锁
            rwl.readLock().unlock();
        }
    }
}

结束语

本篇到此ReentrantReadWriteLock的共享锁获取和释放的源码就分析完了,下一篇将对信号量Semaphore进行分析。

如果本篇对你有帮助,请点个赞再走,谢谢大家!

原创文章 55 获赞 76 访问量 17万+

猜你喜欢

转载自blog.csdn.net/cool_summer_moon/article/details/106036160