Skip to content

ReentrantLock 详解

synchronized 能做的它都能做,synchronized 做不了的它也能做。

基本概念

ReentrantLock(可重入锁)是 Lock 接口最常用的实现,意思是同一线程可以反复获取同一把锁

java
ReentrantLock lock = new ReentrantLock();

lock.lock();     // 第一次
lock.lock();     // 第二次(可重入)
lock.lock();     // 第三次
try {
    // ...
} finally {
    lock.unlock(); // count = 2
    lock.unlock(); // count = 1
    lock.unlock(); // count = 0,锁释放
}

与 synchronized 对比

特性synchronizedReentrantLock
获取/释放自动手动
公平/非公平非公平可配置
tryLock
超时
中断
多条件变量
可重入

五种获取锁的方式

1. lock():阻塞获取

最基本的方式,获取不到就一直等:

java
Lock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();
}

2. tryLock():不阻塞尝试

立即返回,获取成功返回 true,失败返回 false:

java
Lock lock = new ReentrantLock();

if (lock.tryLock()) {
    try {
        // 获取到了
    } finally {
        lock.unlock();
    }
} else {
    // 没获取到,执行其他逻辑
}

3. tryLock(timeout):带超时的尝试

等待一段时间,获取不到就放弃:

java
Lock lock = new ReentrantLock();

if (lock.tryLock(5, TimeUnit.SECONDS)) {  // 等 5 秒
    try {
        // 获取到了
    } finally {
        lock.unlock();
    }
} else {
    System.out.println("等不及了");
}

4. lockInterruptibly():可中断的获取

等待过程中可以被中断:

java
Lock lock = new ReentrantLock();

try {
    lock.lockInterruptibly();  // 等锁时可以被 interrupt()
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
} catch (InterruptedException e) {
    // 被中断了
}

5. newCondition():条件变量

创建条件变量,实现更灵活的等待/通知:

java
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    while (!conditionMet) {
        condition.await();  // 等待
    }
    // 继续执行
    condition.signal();     // 通知
} finally {
    lock.unlock();
}

代码示例

示例一:基本计数器

java
public class Counter {

    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

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

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

示例二:tryLock 避免死锁

java
public class AccountTransfer {

    private final ReentrantLock lock = new ReentrantLock();
    private int balance = 0;

    // 转账:同时操作两个账户
    public boolean transfer(AccountTransfer target, int amount) {
        // 尝试获取本账户锁,最多等 1 秒
        if (!this.lock.tryLock(1, TimeUnit.SECONDS)) {
            return false;  // 超时放弃
        }
        try {
            // 尝试获取目标账户锁,最多等 1 秒
            if (!target.lock.tryLock(1, TimeUnit.SECONDS)) {
                return false;
            }
            try {
                if (this.balance >= amount) {
                    this.balance -= amount;
                    target.balance += amount;
                    return true;
                }
                return false;
            } finally {
                target.lock.unlock();
            }
        } finally {
            this.lock.unlock();
        }
    }
}

示例三:可中断等待

java
public class InterruptibleTask {

    private final ReentrantLock lock = new ReentrantLock();

    public void process() throws InterruptedException {
        lock.lockInterruptibly();  // 可以被中断的获取
        try {
            // 模拟处理
            Thread.sleep(5000);
            System.out.println("处理完成");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InterruptibleTask task = new InterruptibleTask();

        Thread t1 = new Thread(() -> {
            try {
                task.process();
            } catch (InterruptedException e) {
                System.out.println("线程被中断");
            }
        }, "T1");

        Thread t2 = new Thread(() -> {
            try {
                task.process();
            } catch (InterruptedException e) {
                System.out.println("线程被中断");
            }
        }, "T2");

        t1.start();
        Thread.sleep(100);
        t2.start();

        Thread.sleep(500);
        t2.interrupt();  // 中断 t2
    }
}

示例四:查询锁状态

java
public class LockStateDemo {

    private final ReentrantLock lock = new ReentrantLock();

    public void demo() {
        // 获取锁
        lock.lock();
        try {
            System.out.println("是否被锁定: " + lock.isLocked());           // true
            System.out.println("当前线程持有: " + lock.isHeldByCurrentThread());  // true
            System.out.println("持有次数: " + lock.getHoldCount());        // 1

            // 再获取一次(可重入)
            lock.lock();
            try {
                System.out.println("再次获取后,持有次数: " + lock.getHoldCount());  // 2
            } finally {
                lock.unlock();
            }

            System.out.println("释放一次后,持有次数: " + lock.getHoldCount());  // 1
            System.out.println("等待队列长度: " + lock.getQueueLength());    // 0
        } finally {
            lock.unlock();
        }
    }
}

示例五:生产者-消费者

java
public class ProducerConsumer {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity = 10;

    public void produce(int value) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() >= capacity) {
                notFull.await();  // 队列满,等
            }
            queue.offer(value);
            System.out.println("生产: " + value);
            notEmpty.signal();   // 通知队列非空
        } finally {
            lock.unlock();
        }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();  // 队列空,等
            }
            int value = queue.poll();
            System.out.println("消费: " + value);
            notFull.signal();     // 通知队列非满
            return value;
        } finally {
            lock.unlock();
        }
    }
}

公平锁 vs 非公平锁

java
// 非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();

// 公平锁(按等待顺序)
ReentrantLock fairLock = new ReentrantLock(true);
对比非公平锁公平锁
吞吐量
等待顺序不保证FIFO
饥饿风险存在

默认用非公平锁,因为:

  1. 性能更好(减少线程切换)
  2. 公平性在大多数场景不是必需的

用公平锁的场景:需要严格按顺序执行,如任务调度。

总结

ReentrantLock 的五种获取锁方式:

方法阻塞超时可中断适用场景
lock()简单互斥
tryLock()避免死锁
tryLock(t, u)超时控制
lockInterruptibly()可中断等待
newCondition()---条件等待

记住:lock 必须在 finally 中释放!

基于 VitePress 构建