在 Java 中,显式锁(Explicit Lock) 是相对于 synchronized 隐式锁而言的,指通过代码手动控制锁的获取、释放过程的锁机制,核心接口是 java.util.concurrent.locks.Lock,最常用实现类是 ReentrantLock。显式锁提供了比 synchronized 更灵活的锁操作能力,同时保留了线程安全的核心特性。
一、显式锁的核心优势(对比 synchronized)
| 特性 | 显式锁(Lock) | 隐式锁(synchronized) |
|---|---|---|
| 锁获取 / 释放 | 手动调用 lock()/unlock(),必须显式释放 | 自动获取(进入代码块),自动释放(退出 / 异常) |
| 可中断锁 | 支持(lockInterruptibly()) | 不支持(阻塞时无法被中断) |
| 超时获取锁 | 支持(tryLock(long, TimeUnit)) | 不支持(一直阻塞直到获取) |
| 公平锁支持 | 支持(构造函数指定 fair=true) | 不支持(非公平锁,无法配置) |
| 锁状态查询 | 支持(isLocked()/hasQueuedThreads()) | 不支持(无公开 API 查询) |
| 条件变量(Condition) | 支持(多个 Condition 绑定一个 Lock) | 支持(仅一个,通过 wait()/notify()) |
| 性能(高并发) | 竞争激烈时性能更稳定 | 低并发友好,高并发下性能可能波动 |
二、核心接口与实现类
1. 顶层接口:Lock
定义了显式锁的核心操作,必须实现的方法:
public interface Lock {
// 阻塞获取锁(不可中断,直到获取成功)
void lock();
// 可中断获取锁(阻塞时可被其他线程中断,抛出 InterruptedException)
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁(非阻塞):成功返回 true,失败返回 false(立即返回,不阻塞)
boolean tryLock();
// 超时尝试获取锁:在指定时间内获取成功返回 true,超时/中断返回 false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁(必须手动调用,否则会导致锁泄漏)
void unlock();
// 创建绑定到当前锁的条件变量(Condition)
Condition newCondition();
}
2. 常用实现类:ReentrantLock
- 可重入锁:同一线程可多次获取锁(与
synchronized一致),解锁次数需与加锁次数一致,否则锁无法释放。 - 公平 / 非公平锁:
- 非公平锁(默认):获取锁时不排队,允许 “插队”,性能更高(大多数场景首选)。
- 公平锁:按线程请求顺序分配锁,避免饥饿,但性能开销更大(需维护等待队列)。
- 构造函数:
// 非公平锁(默认) ReentrantLock lock = new ReentrantLock(); // 公平锁 ReentrantLock fairLock = new ReentrantLock(true);
三、显式锁的使用规范
核心原则:必须在 finally 块中释放锁,避免因异常导致锁泄漏(线程持有锁但未释放,其他线程无法获取)。
1. 基础用法(不可中断锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ExplicitLockDemo {
// 初始化非公平锁
private static final Lock lock = new ReentrantLock();
private static int count = 0;
public static void increment() {
// 1. 获取锁
lock.lock();
try {
// 2. 临界区(线程安全操作)
count++;
} finally {
// 3. 释放锁(必须在 finally 中,确保异常时也释放)
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
// 启动 1000 个线程测试
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(ExplicitLockDemo::increment);
threads[i].start();
}
// 等待所有线程结束
for (Thread t : threads) {
t.join();
}
System.out.println("最终计数:" + count); // 输出 1000(线程安全)
}
}
2. 可中断锁(lockInterruptibly())
适用于 “获取锁时允许被中断” 的场景(如超时任务、用户取消操作):
public static void interruptibleLockDemo() throws InterruptedException {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
// 尝试获取锁(可被中断)
lock.lockInterruptibly();
try {
System.out.println("线程1获取锁成功,开始执行");
Thread.sleep(5000); // 模拟长时间操作
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("线程1获取锁时被中断");
}
});
Thread t2 = new Thread(() -> {
try {
lock.lockInterruptibly(); // 阻塞等待 t1 释放锁
System.out.println("线程2获取锁成功");
} catch (InterruptedException e) {
System.out.println("线程2获取锁时被中断");
}
});
t1.start();
Thread.sleep(1000); // 确保 t1 先获取锁
t2.start();
Thread.sleep(1000); // 让 t2 阻塞在获取锁
t2.interrupt(); // 中断 t2 的锁等待
}
3. 超时获取锁(tryLock(long, TimeUnit))
避免线程无限期阻塞,适用于 “超时放弃” 的场景:
public static void tryLockWithTimeout() throws InterruptedException {
Lock lock = new ReentrantLock();
Thread t = new Thread(() -> {
boolean acquired = false;
try {
// 尝试在 2 秒内获取锁
acquired = lock.tryLock(2, TimeUnit.SECONDS);
if (acquired) {
System.out.println("线程获取锁成功");
} else {
System.out.println("线程超时未获取到锁,放弃操作");
}
} catch (InterruptedException e) {
System.out.println("线程被中断");
} finally {
// 只有获取锁成功后,才需要释放
if (acquired) {
lock.unlock();
}
}
});
lock.lock(); // 主线程先获取锁
t.start();
Thread.sleep(3000); // 主线程持有锁 3 秒(超过 t 的超时时间)
lock.unlock();
}
4. 条件变量(Condition)
Condition 是显式锁的 “等待 / 通知” 机制,相比 synchronized 的 wait()/notify() 更灵活(一个锁可绑定多个 Condition,实现更精细的线程通信)。
核心方法:
await():当前线程释放锁,进入条件等待队列(类似wait())。signal():唤醒条件等待队列中的一个线程(类似notify())。signalAll():唤醒条件等待队列中的所有线程(类似notifyAll())。
示例:生产者 - 消费者模型(多个生产者 / 消费者,基于队列)
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
private static final Lock lock = new ReentrantLock();
// 条件1:队列不为空(消费者等待此条件)
private static final Condition notEmpty = lock.newCondition();
// 条件2:队列不为满(生产者等待此条件)
private static final Condition notFull = lock.newCondition();
private static final Queue<Integer> queue = new LinkedList<>();
private static final int MAX_SIZE = 5;
// 生产者:向队列添加元素
public static void produce(int num) throws InterruptedException {
lock.lock();
try {
// 队列满时,生产者等待(释放锁)
while (queue.size() == MAX_SIZE) {
System.out.println("队列满,生产者等待");
notFull.await(); // 等待“队列不为满”条件
}
queue.offer(num);
System.out.println("生产者生产:" + num + ",队列大小:" + queue.size());
notEmpty.signal(); // 唤醒等待“队列不为空”的消费者
} finally {
lock.unlock();
}
}
// 消费者:从队列获取元素
public static Integer consume() throws InterruptedException {
lock.lock();
try {
// 队列空时,消费者等待(释放锁)
while (queue.isEmpty()) {
System.out.println("队列空,消费者等待");
notEmpty.await(); // 等待“队列不为空”条件
}
Integer num = queue.poll();
System.out.println("消费者消费:" + num + ",队列大小:" + queue.size());
notFull.signal(); // 唤醒等待“队列不为满”的生产者
return num;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
// 启动 2 个生产者
new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
produce(i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
for (int i = 11; i <= 20; i++) {
produce(i);
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 启动 3 个消费者
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
while (true) {
consume();
Thread.sleep(800);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
四、显式锁的注意事项
- 必须释放锁:
lock()后必须在finally中调用unlock(),否则线程异常时会导致锁泄漏(其他线程永远无法获取锁)。 - 可重入性:同一线程多次
lock()需对应多次unlock(),否则锁无法完全释放(例如:加锁 2 次,解锁 1 次,其他线程仍无法获取)。 - 公平锁的性能开销:公平锁需维护等待队列的顺序,高并发下性能比非公平锁低,非特殊需求(如避免线程饥饿)不建议使用。
- Condition 的等待条件:
await()前必须先获取锁,且需在while循环中判断条件(而非if),避免 “虚假唤醒”(线程被唤醒后条件可能已不满足)。 - 与
synchronized的选择:- 简单场景(如单临界区、无特殊需求):优先用
synchronized(语法简洁,JVM 优化更成熟)。 - 复杂场景(如可中断、超时、公平锁、多条件变量):用
ReentrantLock。
- 简单场景(如单临界区、无特殊需求):优先用
五、其他显式锁实现类
除了 ReentrantLock,Java 还提供了其他常用显式锁:
ReentrantReadWriteLock:读写锁(分离读锁和写锁),支持 “多读单写”,读操作并发执行,写操作互斥,适用于 “读多写少” 场景(如缓存、配置读取)。StampedLock:Java 8 新增的乐观读写锁,性能优于ReentrantReadWriteLock,支持乐观读、悲观读、写锁三种模式。
总结
显式锁(Lock 接口)是 Java 并发编程中灵活、强大的锁机制,核心优势是支持可中断、超时获取、公平锁、多条件变量等特性。使用时需严格遵循 “获取锁后在 finally 中释放” 的规范,避免锁泄漏。在简单场景下 synchronized 更简洁高效,复杂场景下 ReentrantLock 更具优势。
749

被折叠的 条评论
为什么被折叠?



