源码解读之(五)ReentrantReadWriteLock(下)

上篇的中篇主要讲的ReentrantReadWriteLockReadLock的加锁和减锁过程:
源码解读之(五)ReentrantReadWriteLock(中)
这篇下篇主要来讲解讲解ReentrantReadWriteLockWriteLock的加锁和减锁过程。


四、WriteLock

ReentrantReadWriteLockWriteLock加锁解锁过程依赖于AbstractQueuedSynchronizer(AQS)类,所以有些相同的逻辑可以看看ReentrantLock的逻辑。

1、加锁过程

  • WriteLock的lock()内部通过sync.acquire(1)获取锁。
  • 尝试通过tryAcquire获取写锁,如果获取成功那么就成功占用写锁。
  • 获取写锁失败后,将当前线程添加到写锁唤醒队列当中acquireQueued(addWaiter(Node.EXCLUSIVE), arg))。
  • acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的操作逻辑和ReentrantLock的逻辑一致。
public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;

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

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

public final void acquire(int arg) {
    // 1、先尝试获取锁tryAcquire
    // 2、获锁失败就addWaiter操作
    // 3、acquireQueued判断是否唤醒
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
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)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

tryAcquire过程

  • 获取锁状态state变量,并获取写锁占用的计数值。
  • 当前state不为0,如果写锁状态为0说明读锁被占用,返回锁占用失败。
  • 锁状态state不为空且占锁线程为当前线程,说明锁被其他线程占用返回锁占用失败。
  • 写锁重入数溢出,返回锁占用失败。
  • 如果写锁阻塞 或者 设置state状态失败,返回锁占用失败。
  • 设置当前锁占用线程为当前线程,返回锁占用成功。
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    // 获取锁状态state变量
    int c = getState();
    // 获取写锁占用的计数
    int w = exclusiveCount(c);
    // 如果锁状态state不为0
    if (c != 0) {
        // 1、当前state不为0,如果写锁状态为0说明读锁此时被占用,说明锁被读锁占用
        // 2、锁状态state不为空且占锁线程为当前线程(属于锁重入),说明锁被其他线程占用
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 写锁重入数溢出
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 写锁获取成功返回成功标记
        setState(c + acquires);
        return true;
    }
    // 如果写锁阻塞 或者 设置state状态失败,那么就代表获锁失败
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 设置当前锁占用线程为当前线程
    setExclusiveOwnerThread(current);
    return true;
}

2、解锁过程

  • WriteLock的unlock()内部通过sync. release(1)释放锁。
  • 尝试通过tryRelease()方法来释放锁并唤醒下一个等待线程。
  • 在唤醒过程中需要仔细看看读写锁等待线程唤醒的细节。
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
	
	// 如果状态取消,从尾部向后遍历以找到实际的未取消的节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

tryRelease过程

  • 判断当前线程和锁占用线程不一致isHeldExclusively()抛出异常。
  • 锁状态减去当前释放动作传入参数nextc = getState() - releases。
  • 判断锁状态的写状态为0就表明当前线程已经完全释放锁。
  • 当前线程完全释放锁,然后设置锁占用线程为null并设置锁状态。
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章