Java并发编程之可重入锁ReentrantLock

311 阅读4分钟

这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战

ReentrantLock概述

ReentrantLock是可重入的独占锁,同时只能有一个线程可以获得锁,其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。我们先看一下ReentrantLock的类图。ReentrantLock其实也是根据AQS来实现的,并且根据的输入参数来决定其是公平锁还是非公平锁,默认是非公平锁。Sync类直接继承自AQS,它的子类NonfairSync和FairSync 分别实现了获取锁的非公平与公平策略。

image.png

在AQS的state状态值表示线程获取该锁的可重入次数,在默认情况下,state的值为0表示当前锁没有被任何线程持有。当一个线程第一次获取到该锁时会尝试使用CAS设置state值为1,如果CAS成功则当前线程获取了该锁,然后记录锁的持有者为当前线程。在该线程没有释放锁的情况下,第二次获取到锁时,state被修改为2,这就是可重入次数。在该线程释该锁时,会尝试使用CAS让状态值减1,如果减1后状态为0,则释放掉该锁。

ReentrantLock重要方法详解

void lock()方法

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

如上代码所示,ReentrantLock的lock()函数是委托给了Sync类,根据ReentrantLock的构造函数选择sync的实现时NonfailSync还是FairSync。我们接下来先看一下Sync子类NonfairSync的实现。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
    //CAS设置状态值
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1); //调用AQS的acqiure方法
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

在NonfairSync代码中,因为默认的state值为0,所以第一个调用Lock的线程会通过CAS设置状态值为1,CAS成功则表示当前线程获取到了锁,通过setExclusiveOwnerThread设置锁的持有者是当前线程。

如果这时候有其他线程调用lock方法企图获取该锁,CAS会失败,然后会调用AQS的acquire方法。(acquire传入的值为1)

public final void acquire(int arg) {
//调用ReentrantLock重写的tryAcquire方法
//tryAcquire(arg) 如果为false会把当前线程放入AQS阻塞队列
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

我们之前的文章说过,AQS并没有提供tryAcquire方法,我们先看一下非公平锁的实现。

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //获取当前锁的state
    int c = getState();
    //如果当前AQS状态为0
    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抢锁,然后返回true。如果state不为0,则锁已经被某个线程持有,先判断当前线程是否是锁的持有者,如果是则state+1(nextc<0说明可重入次数溢出了),如果不是则返回false。

然后我们再来看一下,FairSync重写的tryAcquire方法是怎样来实现公平的。

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;
}

接下来的很重要

公平锁的tryAcquire策略与非公平类似,不同之处在于在设置CAS之前添加了hasQueuePredecessors方法。

public final boolean hasQueuedPredecessors() {

    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());
}

在上面代码中,返回值为false的时候,意味着当前节点不用去排队,可以分为两种情况。

  1. (h!=t)为false,则直接返回false。这句话的意思是首尾节点相等,当首尾节点都为null是,说明队列为空。所以不用排队;当首尾节点不为null且相等时,说明队列中只有一个节点,当队列中只有一个节点时,根据我们之前AQS中的对acquireQueued()函数的介绍,第二个节点是不参与排队的。

  2. 当(h!=t)返回true时,表示队列中至少有两个不同节点存在;(s = h.next) == null返回false表示头节点有后继节点;s.thread != Thread.currentThread()返回false表示第二个节点的线程就是当前线程,所以也无需排队。

返回值为true的时候,意味着当前节点需要去排队

当(h!=t)返回true时,表示队列中节点数>=2,此时继续判断当((s=h.next)==null)为false时,继续判断(s.thread !=Thread.currentThread())是否为true,如果为true,意思就是头结点有后继节点,并且后继节点不是当前线程,所以当前线程需要去队列中。

void lockInterruptibly()方法

该方法与lock()方法类似,不同点在于它对中断进行响应。当前线程在调用该方法时,如果其它线程调用了当前线程的interrupt()方法,则当前线程会抛出InterruptedException异常。

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
        //如果当前线程被中断,则直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
        //尝试获取资源
    if (!tryAcquire(arg))
    //调用AQS可被中断的方法
        doAcquireInterruptibly(arg);
}

boolean tryLock()方法

表示尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁并返回true,否则返回false。本方法不会引起当前线程阻塞。

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
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;
}

void unlock()方法

尝试释放锁,如果当前线程持有锁,则调用该方法会让该线程对该线程持有的AQS中的state值减1,如果减去1后,state=0,则当前线程释放该锁,否则只是次数state-1。如果线程没有持有该锁,则会抛出IllegalMonitorStateException异常。

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;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}