ReentrantLock 深度解析

ReentrantLock 深度解析

在 Java 并发编程中,锁是实现线程同步的关键机制。除了内置的 synchronized 关键字,java.util.concurrent.locks 包提供了更灵活、功能更强大的锁机制,其中 ReentrantLock 是最常用的实现之一。

1. ReentrantLock 简介

1.1 什么是 ReentrantLock

ReentrantLock 是 Lock 接口的一个实现类,它提供了与 synchronized 关键字相似的互斥和内存可见性功能,但拥有更高的灵活性和可控性。正如其名称所示,ReentrantLock 是一种“可重入”的锁,这意味着持有锁的线程可以多次获取该锁而不会被自己阻塞。

1.2 为什么使用 ReentrantLock

虽然 synchronized 关键字在大多数情况下都能满足并发编程的需求,但 ReentrantLock 提供了 synchronized 不具备的一些高级特性,使其在某些场景下更具优势:

可中断锁: ReentrantLock 提供了 lockInterruptibly() 方法,允许在等待锁的过程中响应中断,这对于避免死锁和提高程序的响应性非常重要。尝试非阻塞地获取锁: tryLock() 方法允许线程尝试获取锁,如果锁已被其他线程持有,则立即返回 false,而不会一直阻塞。这使得线程可以执行其他任务,而不是无限期地等待锁。带超时地获取锁: tryLock(long timeout, TimeUnit unit) 方法允许线程在指定的时间内尝试获取锁,如果超时仍未获取到锁,则返回 false。这可以有效避免线程长时间阻塞。公平性选择: ReentrantLock 允许选择公平锁或非公平锁。公平锁会按照请求的顺序授予锁,而非公平锁则可能允许“插队”,这在某些性能敏感的场景下可能更有利。条件变量: ReentrantLock 可以与 Condition 接口配合使用,实现更细粒度的线程间通信。一个 Lock 对象可以创建多个 Condition 对象,每个 Condition 对象都对应一个等待队列,这比 synchronized 的 wait()/notify()/notifyAll() 机制更加灵活。

2. ReentrantLock 核心特性

2.1 可重入性 (Reentrancy)

可重入性是 ReentrantLock 的一个重要特性。当一个线程获取了 ReentrantLock 后,它可以再次获取该锁而不会被自己阻塞。ReentrantLock 内部维护了一个“持有计数器”(hold count),每当线程成功获取锁时,计数器加 1;每当线程释放锁时,计数器减 1。只有当计数器归零时,锁才真正被释放,其他线程才能获取该锁。

2.2 可中断锁 (Interruptible Lock)

ReentrantLock 提供了 lockInterruptibly() 方法,允许线程在等待锁的过程中响应中断。如果一个线程在等待 ReentrantLock 时被中断,它将抛出 InterruptedException 异常并停止等待。这使得程序能够更好地处理死锁情况,并提高程序的健壮性。

2.3 公平性 (Fairness)

ReentrantLock 提供了两种锁的模式:公平锁和非公平锁。在创建 ReentrantLock 实例时,可以通过构造函数指定是否为公平锁:

公平锁: 当锁可用时,等待时间最长的线程将优先获取锁。这可以避免线程饥饿,但可能会带来性能开销。非公平锁: 当锁可用时,新请求的线程可能会“插队”获取锁,即使有其他线程已经等待了很长时间。非公平锁通常具有更高的吞吐量,因为它们减少了线程切换的开销。

默认情况下,ReentrantLock 是非公平锁。

2.4 条件变量 (Condition Variables)

ReentrantLock 通过 newCondition() 方法提供了 Condition 接口的实现。Condition 对象可以看作是 synchronized 关键字中 wait()/notify()/notifyAll() 方法的替代品,但它提供了更强大的功能。一个 ReentrantLock 可以关联多个 Condition 对象,每个 Condition 对象都维护一个独立的等待队列。这使得线程可以根据不同的条件进行等待和唤醒,从而实现更精细的线程协作。

3. ReentrantLock 基本用法

3.1 lock() 和 unlock()

lock() 方法用于获取锁,如果锁已被其他线程持有,则当前线程会一直阻塞直到获取到锁。unlock() 方法用于释放锁。为了确保锁在任何情况下都能被释放,通常会将 unlock() 方法放在 finally 块中。

import java.util.concurrent.locks.ReentrantLock;

public class Counter {

private int count = 0;

private ReentrantLock lock = new ReentrantLock();

public void increment() {

lock.lock(); // 获取锁

try {

count++;

System.out.println(Thread.currentThread().getName() + " incremented count to: " + count);

} finally {

lock.unlock(); // 释放锁

}

}

public static void main(String[] args) {

Counter counter = new Counter();

Runnable task = () -> {

for (int i = 0; i < 3; i++) {

counter.increment();

}

};

Thread t1 = new Thread(task, "Thread-1");

Thread t2 = new Thread(task, "Thread-2");

t1.start();

t2.start();

}

}

3.2 tryLock()

tryLock() 方法尝试获取锁,如果锁可用则立即获取并返回 true,否则返回 false。它不会阻塞当前线程。

import java.util.concurrent.locks.ReentrantLock;

public class Worker implements Runnable {

private ReentrantLock lock;

private String name;

public Worker(ReentrantLock lock, String name) {

this.lock = lock;

this.name = name;

}

@Override

public void run() {

if (lock.tryLock()) { // 尝试获取锁

try {

System.out.println(name + " acquired lock");

// 模拟工作

try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }

} finally {

lock.unlock(); // 释放锁

System.out.println(name + " released lock");

}

} else {

System.out.println(name + " could not acquire lock");

}

}

public static void main(String[] args) {

ReentrantLock lock = new ReentrantLock();

new Thread(new Worker(lock, "Worker-1")).start();

new Thread(new Worker(lock, "Worker-2")).start();

}

}

3.3 tryLock(long timeout, TimeUnit unit)

此方法尝试在指定的时间内获取锁。如果在超时时间内成功获取锁,则返回 true,否则返回 false。

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.ReentrantLock;

public class TimedWorker implements Runnable {

private ReentrantLock lock;

private String name;

public TimedWorker(ReentrantLock lock, String name) {

this.lock = lock;

this.name = name;

}

@Override

public void run() {

try {

if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试在1秒内获取锁

try {

System.out.println(name + " acquired lock");

// 模拟工作

try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }

} finally {

lock.unlock();

System.out.println(name + " released lock");

}

} else {

System.out.println(name + " could not acquire lock within timeout");

}

} catch (InterruptedException e) {

System.out.println(name + " was interrupted while waiting for lock");

}

}

public static void main(String[] args) {

ReentrantLock lock = new ReentrantLock();

new Thread(new TimedWorker(lock, "TimedWorker-1")).start();

new Thread(new TimedWorker(lock, "TimedWorker-2")).start();

}

}

3.4 lockInterruptibly()

lockInterruptibly() 方法在获取锁时可以响应中断。如果线程在等待锁的过程中被中断,它将抛出 InterruptedException。

import java.util.concurrent.locks.ReentrantLock;

public class InterruptibleWorker implements Runnable {

private ReentrantLock lock;

private String name;

public InterruptibleWorker(ReentrantLock lock, String name) {

this.lock = lock;

this.name = name;

}

@Override

public void run() {

try {

lock.lockInterruptibly(); // 可中断地获取锁

try {

System.out.println(name + " acquired lock");

// 模拟工作

while (!Thread.currentThread().isInterrupted()) {

// 持续工作直到被中断

}

} finally {

lock.unlock();

System.out.println(name + " released lock");

}

} catch (InterruptedException e) {

System.out.println(name + " was interrupted while waiting for lock or working");

}

}

public static void main(String[] args) throws InterruptedException {

ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(new InterruptibleWorker(lock, "InterruptibleWorker-1"));

Thread t2 = new Thread(new InterruptibleWorker(lock, "InterruptibleWorker-2"));

t1.start();

t2.start();

// 让t1先获取锁并工作一段时间

Thread.sleep(100);

// 中断t2,使其停止等待锁

t2.interrupt();

}

}

4. ReentrantLock 与 synchronized 的区别

特性synchronizedReentrantLock实现方式Java 关键字,由 JVM 隐式实现Java 类,通过 API 显式实现锁的获取自动获取和释放,无法中断,无法尝试获取手动获取和释放,可中断,可尝试获取,可超时获取公平性非公平锁可选择公平锁或非公平锁条件变量wait()/notify()/notifyAll(),所有等待线程在同一个队列Condition 接口,可创建多个条件变量,每个条件变量对应一个等待队列性能在 Java 6 之后,性能与 ReentrantLock 接近,甚至在某些场景下更好在高并发竞争下通常表现更好,但需要手动管理锁的释放5. 总结

ReentrantLock 作为 java.util.concurrent.locks 包中的重要组成部分,为 Java 并发编程提供了比 synchronized 关键字更强大、更灵活的锁机制。它提供了可重入性、可中断锁、公平性选择以及条件变量等高级特性,使得开发者能够更精细地控制线程的同步行为。在选择使用 synchronized 还是 ReentrantLock 时,应根据具体的应用场景和需求进行权衡。对于简单的同步需求,synchronized 仍然是首选;而对于需要更高级功能(如可中断、超时、公平性或多条件等待)的复杂并发场景,ReentrantLock 无疑是更合适的选择。

相关推荐