# 前言

Reentrant 是 Java 提供的同步锁,基于 AQS 实现了公平和非公平的锁。

# 使用示例

import java.util.concurrent.locks.ReentrantLock;
public class Test {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Runnable runnable = () -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " 拿到了锁。");
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                // ...
            } finally {
                System.out.println(Thread.currentThread().getName() + " 将要释放锁。");
                lock.unlock();
            }
        };
        new Thread(runnable, "thread1").start();
        new Thread(runnable, "thread2").start();
    }
}

输出结果为:

thread1 拿到了锁。
thread1 将要释放锁。
thread2 拿到了锁。
thread2 将要释放锁。

可以看出,thread1 和 thread2 不能同时拿到锁,thread2 要等 thread1 释放锁后才能获取到锁。

# 源码分析

ReentrantLock 实现了 Lock 接口,可以通过构造函数的参数指定是否公平锁,默认是非公平锁。公平锁需要按照请求获取锁的顺序来加锁,所以会消耗 CPU,也会降低吞吐量。

# Lock

Lock 接口提供了加锁和释放锁的方法。

  • void lock () - 加锁
  • void lockInterruptibly () - 加锁,可响应中断
  • boolean tryLock () - 尝试加锁,轻量级操作
  • boolean tryLock (long time, TimeUnit unit) - 尝试加锁,超时放弃,可响应中断
  • void unlock () - 释放锁
  • Condition newCondition () - 等待条件对象

# Sync

Sync 是一个抽象类,实现了 AQS,提供基础的同步控制。

# nonfairTryAcquire

非公平方式获取锁,不讲究先来后到,如果持有锁的线程已释放掉锁,直接抢锁。

final boolean nonfairTryAcquire(int acquires) {
    // 当前线程
    final Thread current = Thread.currentThread();
    // 已获取到锁的数量
    int c = getState();
    // 没有其他线程获取到锁
    if (c == 0) {
        // 修改 state,标识自己已抢到锁
        if (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;
}

# tryRelease

最终返回的是是否已经完全释放掉锁。

protected final boolean tryRelease(int releases) {
    // 最新锁的数量
    int c = getState() - releases;
    // 持有锁的线程不是自己,不能释放锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 是否已完全释放锁
    boolean free = false;
    // 最新持有锁的数量为 0,说明本次释放会彻底释放掉锁,将锁的持有者设置为 null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 更新最新持有锁的数量
    setState(c);
    // 返回是否完全释放掉了锁
    return free;
}

# NonfairSync

非公平锁直接使用 Sync 提供的基础方法来加锁和释放锁。

# FairSync

公平锁加锁时,与非公平锁唯一的区别就是,如果当前没有其他线程持有锁,会判断下队列中有没有已等待的线程,如果没有的话,才会抢锁,否则会进等待队列。释放锁的逻辑就和非公平锁一样了,都是更新 state 减去释放的数量。

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

# 场景说明

结合 AQS 来说明下,多线程场景下,对公平锁进行获取锁和释放锁的流程。

# ① 线程 a 获取锁

此时还没有其他线程来获取锁,所以可以直接获取到锁。

# ② 线程 b 获取锁

此时线程 a 正在持有锁,所以线程 b 无法直接获取到锁,需要加入等待队列。等待队列中没有节点,head 和 tail 也还没有初始化,需要初始化队列。线程 b 调用 LockSupport.park () 进入了等待状态。

线程 b 获取锁

# ③ 线程 a 释放锁

线程 a 释放掉了锁,将线程 b 唤醒,然后线程 b 从上次进入等待的位置开始继续获取锁。如果线程 b 在等待期间,线程被标记中断,那么是要到拿到锁之后才响应的中断。

线程 a 释放锁

# ④ 线程 b 释放锁

线程 b 直接释放锁,由于后面没有等待的线程了,也不需要唤醒后面的线程。

# 总结

ReentrantLock 之所以逻辑简单,是因为有强大的 AQS 支持,它只需要实现下对 state 的操作即可,如果更新 state 失败,没有获取或释放锁,AQS 会进行入队、等待和唤醒的操作。