ReentrantLock通过lock()和unlock()方法实现手动加锁与释放,确保线程安全;其相比synchronized提供更灵活的锁控制,如可中断、超时获取、公平性选择及条件变量支持;使用时需在finally块中释放锁以避免死锁,推荐非公平锁提升性能,合理控制锁粒度,并利用Condition实现复杂线程协作。

ReentrantLock在Java并发编程中扮演着关键角色,它提供了一种可重入的互斥锁机制,功能上与
synchronized
lock()
unlock()
ReentrantLock的核心用法,简单来说,就是围绕着
lock()
unlock()
lock()
finally
unlock()
import java.util.concurrent.locks.ReentrantLock;
public class SharedResource {
private final ReentrantLock accessLock = new ReentrantLock();
private int counter = 0;
public void increment() {
accessLock.lock(); // 尝试获取锁,如果锁被占用,当前线程会在这里等待
try {
// 这是临界区代码,只有获取到锁的线程才能执行
counter++;
System.out.println(Thread.currentThread().getName() + " incremented counter to: " + counter);
} finally {
accessLock.unlock(); // 确保锁在任何情况下都能被释放
}
}
public int getCounter() {
return counter;
}
public static void main(String[] args) throws InterruptedException {
SharedResource resource = new SharedResource();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
resource.increment();
}
};
Thread t1 = new Thread(task, "Worker-1");
Thread t2 = new Thread(task, "Worker-2");
t1.start();
t2.start();
t1.join(); // 等待线程t1执行完毕
t2.join(); // 等待线程t2执行完毕
System.out.println("Final counter value: " + resource.getCounter()); // 预期输出 2000
}
}你看,这个例子里
increment()
accessLock.lock()
accessLock.unlock()
finally
try
这个问题啊,几乎每个Java开发者在接触并发编程时都会遇到。ReentrantLock和
synchronized
立即学习“Java免费学习笔记(深入)”;
synchronized
ReentrantLock则是一个实实在在的类,你需要手动去实例化它,然后用
lock()
unlock()
synchronized
lockInterruptibly()
tryLock()
true
false
tryLock(long timeout, TimeUnit unit)
ReentrantLock(boolean fair)
newCondition()
Object
wait()/notify()
所以,我的选择策略通常是这样的:如果只是简单的互斥访问,且对性能没有极致要求,
synchronized
ReentrantLock在构造时可以传入一个布尔值参数来决定它是公平锁还是非公平锁。如果你不传这个参数,它默认会创建一个非公平锁。这两种模式,各有各的特点,选择哪种,得看你的具体需求和对性能的权衡。
非公平锁(Nonfair Lock):这是ReentrantLock的默认行为。当一个线程请求锁时,如果锁当前是可用的,它会直接尝试去获取,而不会去管是否有其他线程已经在等待队列里排队了。这有点像“谁手快谁就可能抢到”的模式。非公平锁的优点在于它的性能通常比公平锁要高,因为它减少了线程上下文切换和排队管理的开销。在大多数情况下,非公平锁是更好的选择,因为它能提供更高的吞吐量。它的缺点是,理论上可能会导致“饥饿”现象,就是某些等待时间较长的线程可能一直获取不到锁,虽然在实际高并发场景中,这种极端情况并不那么常见。
公平锁(Fair Lock):当一个线程请求锁时,如果锁当前被持有,它会老老实实地进入等待队列,并且只有当它是队列中的第一个线程时,才能获取锁。这就像是“排队取号”的模式,先来后到,非常讲究秩序。公平锁的优点是它能够避免饥饿现象,保证所有等待线程最终都能获取到锁。但它的缺点是性能开销相对较大,因为每次获取锁都需要检查等待队列,这会增加线程切换和调度器的负担。
我的建议是,除非你对公平性有非常严格的业务要求,比如在某些资源调度或优先级分配的场景中,否则通常应该优先选择非公平锁。在绝大多数业务应用中,性能往往是更重要的考量因素,而非公平锁在吞吐量上表现得更好。而且,非公平锁带来的饥饿问题在实际应用中,往往可以通过良好的并发模型设计来缓解,或者其影响程度并不至于无法接受。如果你真的非常担心饥饿,可能需要重新审视你的并发模型,或者考虑更高级的无锁(Lock-Free)数据结构。
ReentrantLock虽然功能强大,给我们提供了很多
synchronized
常见的陷阱:
unlock()
try
finally
unlock()
try
unlock()
IllegalMonitorStateException
最佳实践:
finally
try
myLock.lock();
try {
// 你的临界区代码
} finally {
myLock.unlock();
}tryLock()
Condition
newCondition()
Object
wait()/notify()
总而言之,ReentrantLock是一个功能强大的工具,但它的强大也伴随着使用的复杂性。深入理解其工作原理,并遵循这些最佳实践,才能真正发挥它的优势,构建出健壮且高效的并发程序。
以上就是Java中ReentrantLock的核心用法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号