简介
在并发编程中,线程同步是一个极其重要的话题。Java 提供了多种机制来帮助开发者管理多线程环境下的资源共享问题,其中 ReentrantLock 是 java.util.concurrent.locks 包中的一个重要类。相比早期的 synchronized 关键字,ReentrantLock 提供了更为灵活的锁机制,是在高并发场景中优雅解决线程间竞争问题的利器。
目录
基础概念
使用方法
常见实践
最佳实践
小结
参考资料
基础概念
ReentrantLock 是一个可重入的互斥锁,它与 synchronized 同步块类似,但是提供了更多的高级功能。
主要特性
可重入性: 一个线程可以多次获取同一个锁,而不会发生死锁。
公平性: ReentrantLock 可以配置为公平锁和非公平锁,公平锁按照请求锁的顺序分配,而非公平锁则可能让某些线程长时间得不到锁。
可中断锁请求: 在等待锁的过程中,线程可以放弃尝试并响应中断。
超时功能: 可以设定获取锁的时间限制,避免无限等待。
条件变量: 提供了类似于 Object 的 wait/notify 机制,通过 Condition 类来实现更复杂的线程间同步。
使用方法
使用 ReentrantLock 时,通常遵循“锁-尝试-释放”的模式。下面是一个简单的示例:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock(); // 获取锁
try {
// 临界区代码
System.out.println("锁内执行任务");
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Thread t1 = new Thread(example::performTask);
Thread t2 = new Thread(example::performTask);
t1.start();
t2.start();
}
}
公平锁和非公平锁
创建 ReentrantLock 实例时,可以选择其公平性:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
ReentrantLock nonFairLock = new ReentrantLock(false); // 非公平锁
可响应中断的锁
try {
lock.lockInterruptibly();
// 执行任务
} catch (InterruptedException e) {
// 响应中断
} finally {
lock.unlock();
}
超时功能
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 锁内代码
} finally {
lock.unlock();
}
} else {
// 锁获取失败
}
} catch (InterruptedException e) {
// 处理中断
}
常见实践
多条件变量
ReentrantLock 可以创建多个 Condition 实例,分别控制不同的线程状态:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MultiConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
public void awaitCondition1() throws InterruptedException {
lock.lock();
try {
condition1.await();
// condition1 被唤醒后执行的代码
} finally {
lock.unlock();
}
}
public void signalCondition1() {
lock.lock();
try {
condition1.signal();
} finally {
lock.unlock();
}
}
public void awaitCondition2() throws InterruptedException {
lock.lock();
try {
condition2.await();
} finally {
lock.unlock();
}
}
public void signalCondition2() {
lock.lock();
try {
condition2.signal();
} finally {
lock.unlock();
}
}
}
重入锁的嵌套使用
由于 ReentrantLock 是可重入的,同一线程可以在持有锁后再次获取锁,而不会被阻塞:
public void reentrantLockTest() {
lock.lock();
try {
// 第一次获取锁
lock.lock();
try {
// 第二次获取锁
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
最佳实践
确保最终释放锁: 每次获取锁后一定要在 finally 块中释放,以防止异常情况下锁未释放。
优先使用 tryLock: 在可能锁竞争比较激烈的代码区域,建议使用 tryLock 以避免死锁。
选择合适的锁策略: 根据生产环境的需求,选择合适的公平性策略。对于高竞争情况,非公平锁一般性能更好,但可能导致某些线程饥饿。
条件变量的正确使用: 避免产生假唤醒,使用条件变量时通常需要在循环中等待。
小结
ReentrantLock 为 Java 开发者提供了更为强大的同步控制机制,相比于传统的 synchronized,它提供了可中断锁、超时锁、条件变量等功能,有效提升了并发程序的灵活性和性能。理解其实现原理并结合最佳实践,能够帮助开发者在高并发的编程场景下编写出更加健壮和高效的代码。
参考资料
Java Docs: ReentrantLock
《Java 并发编程实战》——Brian Goetz
《Effective Java》第三版——Joshua Bloch
Java线程池
Java 线程同步
通过这篇博客,希望能帮助读者更加深入地理解 ReentrantLock,并能够在实际工作中灵活运用其功能组件实现高效并发编程。