Skip to content

ReentrantLock详解

ReentrantLock是基于AQS框架的一种实现,实现了Lock接口,有一个Sync的内部类,Sync是一个由abstract修饰继承自AbstractQueuedSynchronizer(AQS)的类。

ReentrantLock还有两个内部类NonfairSync和FairSync,前者是非公平锁同步对象,后者是公平锁同步对象,二者都继承自Sync。

零基础可以先这样理解:

ReentrantLock 是一个可以重复进入的互斥锁。

“互斥”表示同一时刻只允许一个线程进入临界区;“可重入”表示同一个线程已经拿到锁以后,可以再次拿同一把锁,不会把自己堵住。可重入次数记录在 AQS 的 state 里。

工作原理图

mermaid
flowchart TD
    A["线程调用 lock"] --> B{"state == 0?"}
    B -- "是" --> C{"CAS 把 state 从 0 改为 1"}
    C -- "成功" --> D["设置 owner 为当前线程"]
    C -- "失败" --> E["进入 AQS 队列等待"]
    B -- "否" --> F{"owner 是当前线程?"}
    F -- "是" --> G["state + 1\n表示重入一次"]
    F -- "否" --> E
    E --> H["线程 park 挂起\n等待 unlock 唤醒"]
    I["当前线程 unlock"] --> J["state - 1"]
    J --> K{"state == 0?"}
    K -- "否" --> L["仍然持有锁\n不唤醒其他线程"]
    K -- "是" --> M["清空 owner\n唤醒队列后继线程"]

所以,lock() 不是简单地“加一个标记”。它还要处理抢锁失败后的排队、同一线程再次进入、释放时是否真正释放完成等问题。

使用 Demo:保护共享变量

java
import java.util.concurrent.locks.ReentrantLock;

public class CounterService {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

unlock() 必须放在 finally 里。否则业务代码抛异常后锁没有释放,后续线程会一直等待。

公平锁和非公平锁的区别

mermaid
flowchart TD
    A["线程请求锁"] --> B{"公平锁?"}
    B -- "是" --> C["先检查 AQS 队列\n前面有人就排队"]
    B -- "否" --> D["先尝试直接 CAS 抢锁\n失败再排队"]
    C --> E["更接近先来先服务\n吞吐量可能低一些"]
    D --> F["吞吐量通常更高\n但等待线程可能被插队"]

默认构造方法创建的是非公平锁,因为多数业务更看重吞吐量。只有当你明确需要接近“先到先得”的等待顺序时,才考虑 new ReentrantLock(true)

构造函数

java
/**
 * 创建一个ReentrantLock实例默认创建非公平锁
 */
public ReentrantLock() {
    sync = new NonfairSync();
}
/**
 * 可以指定公平锁和非公平锁的构造器
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Lock接口

Lock接口定义了一些锁相关的抽象接口,如下:

java
public interface Lock {
    // 获取锁
    void lock();
    
    // 可中断的获取锁
    void lockInterruptibly() throws InterruptedException;
    // 尝试获取锁-只有在调用时锁是空闲的情况下才获取锁
    boolean tryLock();
    // 给定时间内尝试获取锁-如果锁在给定的等待时间内是空闲的,并且当前线程没有被中断,则获取锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放锁。
    void unlock();
    // 返回绑定到此Lock实例的新Condition实例
    Condition newCondition();
}

Sync解说

Sync是继承自AbstractQueuedSynchronizer,ReentrantLock的静态内部类,是同步控制的基础。使用AQS的state代表持有锁的次数。

nonfairTryAcquire方法

Sync内部类中包含非公平锁实现(nonfairTryAcquire),tryAcquire是在Sync的子类中实现的。

非公平锁获取锁方法

java
/**
 * 非公平锁的尝试获取锁
 */
final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取同步状态state值
    int c = getState();
    // state为0,表示当前线程没有持有锁,尝试获取锁
    if (c == 0) {
        // 通过CAS设置state值为acquires
        if (compareAndSetState(0, acquires)) {
            // 设置独占线程为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // state不为0,表示已有线程持有锁,判断独占线程是否为当前线程
    else if (current == getExclusiveOwnerThread()) {
        // 计算新的state值判断是否溢出
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //未溢出讲新值设置给state
        setState(nextc);
        return true;
    }
    return false;
}

NonfairSync和FairSync

NonfairSync和FairSync都继承自Sync,NonfairSync是非公平锁实现,FairSync是公平锁实现。

NonfairSync

源码如下:

java
/**
 * 非公平锁的同步对象
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * 执行加锁 
     * 通过CAS更新同步状态
     * 如果CAS成功则设置独占线程为当前线程,否则执行acquire方法
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

FairSync

源码如下:

java
/**
 * 公平锁同步对象
 */
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * 公平的tryAcquire.  
     * 除非递归调用或没有等待程序,否则不要授予访问权限
     */
    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取同步状态值
        int c = getState();
        // 如果同步状态为0
        if (c == 0) {
            // 查询是否存在线程在等待获取资源,如果没有则通过CAS设置同步状态为acquires,成功则设置独占线程为当前线程;
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //同步状态不为0判断当前线程是否为独占线程
        else if (current == getExclusiveOwnerThread()) {
            // 计算同步状态新值
            int nextc = c + acquires;
            // int类型值溢出抛异常
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            // 更新新值
            setState(nextc);
            return true;
        }
        return false;
    }
}

acquire详见AQS独占模式-获取资源