简介: ReentrantLock是Java中比synchronized更灵活的同步工具,支持可中断、可超时、公平锁及多条件变量控制。本文深入解析其核心特性、Condition精准调度、底层AQS原理,并对比synchronized,助你掌握高并发编程的最佳实践。
在Java并发编程中,synchronized关键字是我们的老朋友,它简单易用,但灵活性不足。当我们需要更复杂的同步控制时,比如尝试获取锁、可中断的锁获取、或者公平锁机制,该怎么办?
java.util.concurrent.locks.ReentrantLock(可重入锁)便是JDK为我们提供的强大答案!它不仅是synchronized的增强版,更是理解JUC包并发设计思想的钥匙。本文将带你深入探索ReentrantLock的奥秘,解锁高并发编程的新技能。
synchronized作为Java原生的同步机制,虽然简单,但存在一些局限性:
ReentrantLock的设计目标:
ReentrantLock的使用遵循一个固定模式:lock() -> try...finally -> unlock()。必须在finally块中释放锁,以确保异常时锁也能被释放。
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 手动获取锁(阻塞直到成功)
try {
count++; // 临界区代码
// 可重入性演示:同一个线程可以再次获取锁
if (count < 10) {
this.increment(); // 递归调用,会再次成功获取锁
}
} finally {
lock.unlock(); // 必须在finally中释放锁!
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}这是ReentrantLock比synchronized灵活的重要体现。
public boolean tryIncrement() {
// 尝试获取锁,立即返回成功与否,不会阻塞线程
if (lock.tryLock()) {
try {
count++;
return true;
} finally {
lock.unlock();
}
} else {
// 没拿到锁,可以先去做别的事情
System.out.println("没能获取到锁,执行其他逻辑...");
return false;
}
}
public boolean tryIncrementWithTimeout() throws InterruptedException {
// 尝试获取锁,最多等待1秒
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
} else {
System.out.println("1秒内都没获取到锁,放弃吧...");
return false;
}
}synchronized在获取锁时是不可中断的,而ReentrantLock提供了可中断的方式。
public void interruptibleIncrement() throws InterruptedException {
// 这个方法允许在等待锁的过程中被其他线程中断
lock.lockInterruptibly(); // 可能会抛出InterruptedException
try {
// 模拟一个长时间操作
while (true) {
count++;
Thread.sleep(1000); // 睡眠中可能被中断
}
} finally {
lock.unlock();
}
}// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
// 创建非公平锁(默认)
ReentrantLock nonFairLock = new ReentrantLock();
ReentrantLock nonFairLock2 = new ReentrantLock(false);选择建议:除非有严格的公平性要求,否则默认使用非公平锁,因为其性能在大多数情况下优于公平锁。
synchronized与wait()/notifyAll()配合使用,所有等待线程都在同一个等待队列里,唤醒时不够精确。ReentrantLock可以与多个Condition(条件变量)关联,实现更精细的线程等待与唤醒。
经典案例:生产者-消费者模型
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumerModel {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 5;
private final ReentrantLock lock = new ReentrantLock();
// 为生产者创建一个条件队列:队列"未满"时才能生产
private final Condition notFull = lock.newCondition();
// 为消费者创建一个条件队列:队列"非空"时才能消费
private final Condition notEmpty = lock.newCondition();
public void produce(int item) throws InterruptedException {
lock.lock();
try {
// 使用while循环防止"虚假唤醒"
while (queue.size() == capacity) {
// 队列已满,生产者等待在"notFull"条件上
System.out.println("队列已满,生产者等待...");
notFull.await(); // 释放锁并等待,被唤醒后重新获取锁
}
queue.offer(item);
System.out.println("生产: " + item + ", 队列大小: " + queue.size());
// 生产了一个,队列肯定不空了,唤醒等待在"notEmpty"上的消费者
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
// 队列为空,消费者等待在"notEmpty"条件上
System.out.println("队列为空,消费者等待...");
notEmpty.await();
}
Integer item = queue.poll();
System.out.println("消费: " + item + ", 队列大小: " + queue.size());
// 消费了一个,队列肯定不满了,唤醒等待在"notFull"上的生产者
notFull.signalAll();
} finally {
lock.unlock();
}
}
}使用多个Condition可以精准地唤醒某一类线程(如只唤醒生产者或只唤醒消费者),避免了notifyAll()唤醒所有线程带来的不必要的性能开销。
ReentrantLock的强大功能,其核心实现依赖于一个强大的基础框架: AbstractQueuedSynchronizer (AQS)。理解AQS是理解大部分JUC同步器的关键。
AQS的核心思想: AQS内部维护了一个** volatile int state(同步状态)和一个FIFO线程等待队列**(CLH队列的变种)。
加锁过程(非公平锁为例):
解锁过程:
AQS通过这个模板,将复杂的线程排队、阻塞、唤醒机制封装起来,只暴露了tryAcquire、tryRelease等方法给子类(如ReentrantLock)去实现,这是一个经典的模板方法模式的应用。
ReentrantLock是Java并发工具库中一把强大的“瑞士军刀”,它提供了比synchronized更精细的控制能力。理解其原理和适用场景,能让你在面对复杂并发问题时拥有更多的选择和更强的解决能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。