一、前言
在并发编程领域,多线程对共享资源的竞争是核心问题,而锁是解决该问题的关键工具。 synchronized 作为 Java 内置的隐式锁,虽使用便捷,但存在功能局限性,比如不支持可中断获取锁、无法设置锁超时等。 ReentrantLock 作为 JUC 包提供的显式锁,正是为了弥补这些不足而诞生。本文将从基础特性和使用方式入手,带大家快速入门 ReentrantLock,同时为后续的源码解析和实战应用做好铺垫。
二、 核心特性
1. 可重入性
可重入性指同一线程可以多次获取同一把锁而不会发生死锁。ReentrantLock 内部通过维护一个锁重入计数器实现该特性,线程首次获取锁时计数器置为 1,后续每次重入计数器加 1,每次释放锁时计数器减 1,直至计数器为 0 时锁才真正释放。
2. 可中断性
ReentrantLock 支持通过 lockInterruptibly() 方法实现可中断的锁获取。当线程在等待获取锁的过程中,若其他线程调用了该线程的 interrupt() 方法,该线程会抛出 InterruptedException 并终止锁等待,这一特性可有效避免死锁。
3. 公平 / 非公平锁策略
-
**公平锁:**线程获取锁的顺序遵循 "先来后到",即等待时间越长的线程越先获取锁,能避免线程饥饿,但会因维护队列顺序带来一定性能损耗。
-
**非公平锁:**线程获取锁时会先尝试直接抢占,抢占失败再进入等待队列,默认采用此策略,吞吐量更高,但可能导致部分线程长期无法获取锁。
4. 超时获取锁
通过 tryLock(long time, TimeUnit unit) 方法,线程可以在指定时间内尝试获取锁,若超时仍未获取到则返回 false ,避免线程永久阻塞在锁获取上。
三、基本使用
1. 基础使用范式
ReentrantLock 为显式锁,需手动调用 lock() 获取锁、 unlock() 释放锁,且必须在 try-finally 块中使用,确保锁最终能释放,防止死锁。
java
import java.util.concurrent.locks.ReentrantLock;
public class BasicLockDemo {
private static final ReentrantLock lock = new ReentrantLock();
private static int count = 0;
public static void increment() {
// 获取锁
lock.lock();
try {
count++;
System.out.println(Thread.currentThread().getName() + " 执行累加,count = " + count);
} finally {
// 释放锁,必须在finally中执行
lock.unlock();
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(BasicLockDemo::increment).start();
}
}
}
2. 可中断锁的使用
使用 lockInterruptibly() 实现可中断锁获取,需处理 InterruptedException 异常:
java
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockDemo {
private static final ReentrantLock lock = new ReentrantLock();
public static void doTask() {
try {
// 可中断的锁获取
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + " 获取到锁,开始执行任务");
Thread.sleep(2000);
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被中断,未获取到锁");
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(InterruptibleLockDemo::doTask, "线程1");
Thread t2 = new Thread(InterruptibleLockDemo::doTask, "线程2");
t1.start();
Thread.sleep(500);
t2.start();
// 中断线程2
t2.interrupt();
}
}
3. 超时获取锁
通过 tryLock() 方法设置锁获取超时时间:
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutLockDemo {
private static final ReentrantLock lock = new ReentrantLock();
public static void tryGetLock() {
try {
// 尝试在1秒内获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread().getName() + " 成功获取锁,执行任务");
Thread.sleep(2000);
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 超时未获取到锁");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 获取锁时被中断");
}
}
public static void main(String[] args) {
new Thread(TimeoutLockDemo::tryGetLock, "线程A").start();
new Thread(TimeoutLockDemo::tryGetLock, "线程B").start();
}
}
4. 公平锁的创建
通过 ReentrantLock 的有参构造函数指定锁的公平性:
java
// 创建公平锁,默认创建的是非公平锁
ReentrantLock fairLock = new ReentrantLock(true);
四、入门案例
分别使用 synchronized 和 ReentrantLock 实现多线程累加,对比两种锁的使用差异:
1. synchronized 实现
java
public class SynchronizedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
for (int i = 0; i < 1000; i++) {
new Thread(counter::increment).start();
}
Thread.sleep(1000);
System.out.println("最终计数:" + counter.getCount());
}
}
2. ReentrantLock 实现
java
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockCounter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockCounter counter = new ReentrantLockCounter();
for (int i = 0; i < 1000; i++) {
new Thread(counter::increment).start();
}
Thread.sleep(1000);
System.out.println("最终计数:" + counter.getCount());
}
}
3. 实现差异对比
-
synchronized 是隐式锁,无需手动释放,代码简洁但功能单一;
-
ReentrantLock 是显式锁,需手动管理锁的获取与释放,但支持可中断、超时获取等高级功能。
五、总结
本文介绍了 ReentrantLock 的核心特性和基础使用方式,包括可重入性、可中断性、公平 / 非公平锁策略、超时获取锁,以及对应的代码实现和入门案例。通过对比 synchronized ,能明显感受到 ReentrantLock 在功能灵活性上的优势。