我眼中的ReentrantLock-插队

184 阅读6分钟

一、简介

ReentrantLock是JDK提供给开发者的一个工具锁,基于AQS(AbstractQueuedSynchronizer)实现,可用于synchronized同功能替换。

其内部提供公平和非公平两种实现方式(Synchronized只有非公平),未获取到锁的线程会加入队列(公平锁和非公平锁加入队列的方式不同),由AQS维护双向队列。

二、简单示例

public static void main(String[] args) {
    testReentrantLock();
}

private static void testReentrantLock() {
    ReentrantLock lock = new ReentrantLock();
    new Thread(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "准备获取锁。。。");
        lock.lock();
        System.out.println(threadName + "获取锁成功。。。");
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            System.out.println(threadName + "休眠失败。。。");
        }
        System.out.println(threadName + "释放锁。。。");
        lock.unlock();
    }, "线程1").start();

    new Thread(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "准备获取锁。。。");
        lock.lock();
        System.out.println(threadName + "获取锁成功。。。");
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            System.out.println(threadName + "休眠失败。。。");
        }
        System.out.println(threadName + "释放锁。。。");
        lock.unlock();
    }, "线程2").start();
}
运行结果
线程1准备获取锁。。。
线程1获取锁成功。。。
线程2准备获取锁。。。
线程1释放锁。。。
线程2获取锁成功。。。
线程2释放锁。。。

三、原理分析

  • 1)ReentrantLock内部维护3个十分重要的属性

state:默认0

head:双向链表头节点,类型为Node

tail:双向链表尾节点,类型为Node

  • 2)案例说明相关方法

上锁方法: lock()

释放锁方法: unlock()

非公平锁

1、使用方式

// 方式1
ReentrantLock rLock = new ReentrantLock();
// 内部创建一个继承自AQS的sync对象
// sync = new NonfairSync();

// 方式2
ReentrantLock rLock = new ReentrantLock(false);
// 内部创建一个继承自AQS的sync对象,实现类为NonFairSync
// sync = fair ? new FairSync() : new NonfairSync();

2、原理分析

  • 2.1 上锁
// 说明:
// 1、客户端调用reentrantLock的lock方法
rLock.lock();
// 2、方法委托Sync实例sync调用lock方法
// 2.1 Sync继承自AbstractQueuedSynchronizer
// 2.2 Sync的lock方法是个抽象方法
sync.lcok();
// 3、子类NonfairSync重写lock方法
ReentrantLock.NonfairSync.lock();
  • 2.2.1 NonfairSync重写后的lock方法
final void lock() {
    // 1、CAS设置state(volatile变量,默认为0)为1
    if (compareAndSetState(0, 1))
        // 2、成功:已经获取到锁,设置独占线程为当前线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 3、失败:未获取到锁,继续获取锁
        acquire(1);
}

注意:对于交替执行的场景,走到步骤2即可返回,而对于存在竞争的场景,CAS失败后,会执行步骤3,继续尝试获取锁

  • 2.2.2 acquire方法,继续获取锁
public final void acquire(int arg) {
    if (
        // 1、尝试获取锁
        !tryAcquire(arg) 
        &&
        // 2、失败:加入AQS队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
    // 3、成功:返回
}
  • 2.2.3 tryAcquire方法,尝试获取锁
// 默认抛出异常,需要子类重写
// NonfairSync重写tryAcquire方法
// 最终调用父类Sync的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 1、获取state变量值
    int c = getState();
    if (c == 0) {
        // 2、默认为0(上文提到),此时表明未被上锁或者其他线程正好释放锁
        // 2.1、再次CAS设置state为1(acquires)
        if (compareAndSetState(0, acquires)) {
            // 2.1.1、成功:已经获取到锁,设置独占线程为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 2、已被上锁且上锁线程为当前线程
        // 2.1 成功,重入:state += acquires
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 3、失败,已被上锁且非当前线程上的锁,或者2.1CAS失败
    return false;
}

若上述acquire方法获取锁成功,直接返回,否则,加入AQS队列,继续获取锁,即:步骤1返回false,会继续步骤2方法调用,回到上一步调用逻辑,如下所示:调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法

public final void acquire(int arg) {
    if (
        // 1、尝试获取锁-失败
        !tryAcquire(arg) 
        &&
        // 2、失败:加入AQS队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • 2.2.4 addWaiter构建节点并入队列

节点类型:Node

Node类属性(只讨论和lock相关的):

waitStatus(默认为0)

prev(上一个节点,默认为空)

next(下一个节点,默认为空)

thread(等待锁的线程,默认为空)

nextWaiter(默认为空)

Node结构如图 image.png

调用方法前,head和tail均为null,结构如图1所示: image.png

private Node addWaiter(Node mode) {
    // 创建节点时:thread为当前线程,nextWaiter为独占模式(mode)
    Node node = new Node(Thread.currentThread(), mode);
    // 大体逻辑:
    // 若队列已构建,CAS设置node为尾节点,并维护node双向队列
    // 若队列未构建,或者队列已构建且CAS失败,调用enq方法维护双向队列
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
  • 2.2.5 调用enq方法维护双向队列
private Node enq(final Node node) {
    // 无限循环,消耗CPU
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // 首次进入,队列为空,CAS设置头节点
            // Node所有值均为默认值
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 待入队的真实节点构建双向队列
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

假设有三个线程,t1线程获取到了锁,t2,t3线程未获取到锁,且按照t2再t3加入队列的顺序,node2节点维护t2,node3节点维护t3,执行完addWaiter方法之后,结果如下图示,node3节点也是tail节点 image.png

t2加入队列,head节点的next指向node2,node2节点的prev节点指向head,同理,t3加入队列,node2节点的next指向node3,node3节点的prev节点指向node2

  • 2.2.6 acquireQueued入队列
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 无限循环
        for (;;) {
            // 若当前节点是head后的一个节点,尝试获取锁tryAcquire
            // 或者
            // 其他线程释放锁后,唤醒
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // 获取锁成功:清空(参考下图1)
                // a)创建是赋值的thread
                // b)入队列设置的prev和next
                // c)保留nextWaiter(lock的流程中无需关注)
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 获取锁失败,会修改p的waitStatus=-1(参考下图2),然后再次回到for循环获取锁
            // 若仍无法获取锁,park当前线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image.png 图1

如图1所示,node2在获取到锁之后,会将自身设置为头节点,此时thread和prev属性均重置为null(参考setHead方法),原head节点的next节点不再指向node2,最后head指向node2节点

注意:尽量参考图2看本实例,因为多线程场景waitStatus可能为-1,这样能更深刻理解node

image.png 图2

如图2所示,node2节点未获取到锁之后会执行shouldParkAfterFailedAcquire方法,并传入其上一个节点(head),方法内部会该节点的waitStaus通过CAS设置为-1,综上可知,node2和node3加入队列后,其上一个节点head和node2的waitStatus均为-1,node3节点的waitStatus仍是0,除非node4节点加入

2.2 释放锁

  • 2.2.1 调用unlock方法
// 说明:
// 1、客户端调用reentrantLock的unlock方法
rLock.unlock();
// 2、方法委托Sync实例sync调用release方法
sync.release(1);
// 3、sync未重写该方法,由父类AbstractQueuedSynchronizer实现
AQS.release();
  • 2.2.2 AbstractQueuedSynchronizer的release方法
public final boolean release(int arg) {
    // 1、尝试释放锁
    if (tryRelease(arg)) {
        // 2、释放成功
        // 2.1 没有竞争,h == null,直接返回
        // 2.2 有竞争,h != nul,由lock分析可知,waitStatus = -1,故而会执行unpark,可回到‘acquireQueued入队列’查看
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    // 释放失败或者未完全释放(重入的场景)
    return false;
}
  • 2.2.3 tryRelease方法,尝试释放锁
// 由子类Sync实现
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 释放releases
    if (Thread.currentThread() != getExclusiveOwnerThread())
        // 报错:非当前线程获取的锁,不能由当前线程释放
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        // 重入释放完全或者简单lock后释放完全
        free = true;
        // 清空锁的独占线程
        setExclusiveOwnerThread(null);
    }
    // 重置state值
    setState(c);
    return free;
}

注意:参考# 2.2.6 acquireQueued入队列 #查看,通过park唤醒头节点后的第一个节点线程,该线程会再次尝试获取锁,若获取成功,会从队列移除该节点。