Java并发编程之ReentrantLock源码(四)

前言

前面三篇文章介绍了ReentrantLock的加锁和释放锁的原理,这篇文章通过源码来了解下ReentrantLock的一些特性是如何实现的,主要包括以下三点:

  • 可重入
  • 可中断
  • 公平锁

可重入

// NonFairSync
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;
}
复制代码

其实前面介绍加锁流程的时候已经提到了重入的流程,这部分代码在ReentrantLock内部类SyncnonfairTryAcquire方法中。

注意看这里的else if语句,如果state不是0表示有线程已经加锁了,这里判断如果当前线程就是持有锁的线程,就把state的值加1,也就是当前线程获取锁成功之后又获取锁,这就是可重入的意思,每重入一次就把state加上1。相对应的,在释放锁时重入几次就需要对state减几次1,直到state0才表示释放了锁。

可中断

// ReentrantLock类
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
// AQS类
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}
// AQS类  
private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

可中断调用的是ReentrantLocklockInterruptibly,最终会调用AQSdoAcquireInterruptibly方法,源码如上。

Java并发编程之ReentrantLock源码(二)这篇文章中分析过获取锁的流程,通过前面的介绍我们了解到线程获取锁失败时就会调用park方法进行阻塞,而此时如果调用了线程的interrupt方法,就会打断park继续执行,parkAndCheckInterrupt方法就会返回Thread.interrupted()也就是true,所以就会执行抛出异常的代码来响应中断

公平锁

// ReentrantLock#Sync类
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;
}
复制代码

上面是非公平锁获取锁的代码,如果state等于0也就是此刻是无锁状态,当前线程就直接尝试CAS修改state来获取锁,而阻塞队列是否有等待的线程这里并没有考虑,这也就是非公平的体现。

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;
}
复制代码

这是公平锁获取锁的代码,我们看到如果state等于0,这里多了一层判断hasQueuedPredecessors,来看下源码

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码
  • h!=t:表示头节点和尾节点指向的不是同一个节点,也就是队列中有其他节点;
  • (s = h.next) == null:表示head的后继节点是null,有可能是头节点还没有指向下一个节点,个人理解参考下面代码,通过CAS设置了tail节点,但是head节点还没有把next指向下一个节点。
if (compareAndSetTail(pred, node)) {
    pred.next = node;
    return node;
}
复制代码
  • s.thread != Thread.currentThread():表示head节点的后继节点不是当前线程的节点。 所以如果队列中有节点,并且第二个节点是null或者不是当前线程的节点,那么当前线程就不能去获取锁以示公平

猜你喜欢

转载自juejin.im/post/7229310860732268601