引言
在多线程编程中,确保对共享资源的同步访问是至关重要的。Java 提供了多种锁机制来实现这一点,其中 ReentrantLock
是一个功能强大且灵活的锁实现。与 synchronized
关键字相比,ReentrantLock
提供了更多的控制和功能。本文将详细介绍 ReentrantLock
的工作原理、使用方法及其应用场景。
什么是 ReentrantLock
ReentrantLock
是 java.util.concurrent.locks
包中的一个类,实现了 Lock
接口。它是一种可重入的互斥锁,意味着同一个线程可以多次获得同一个锁而不会被阻塞。ReentrantLock
提供了比 synchronized
关键字更灵活的锁操作。
ReentrantLock
的基本使用
创建和使用 ReentrantLock
使用 ReentrantLock
进行同步的基本步骤如下:
- 创建
ReentrantLock
实例。 - 在需要同步的代码块前调用
lock()
方法。 - 在代码块结束后调用
unlock()
方法释放锁。
示例代码如下:
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
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();
}
}
}
在上述代码中,我们使用 lock()
方法获取锁,并在 finally
块中调用 unlock()
方法确保锁的释放。
可重入性
ReentrantLock
是可重入的,这意味着同一个线程可以多次获得同一个锁而不会被阻塞:
public class ReentrantExample {
private final ReentrantLock lock = new ReentrantLock();
public void outer() {
lock.lock();
try {
inner();
} finally {
lock.unlock();
}
}
public void inner() {
lock.lock();
try {
// do something
} finally {
lock.unlock();
}
}
}
在上述代码中,outer
方法调用 inner
方法,同一个线程可以多次获得锁而不会发生死锁。
ReentrantLock
的高级功能
公平锁
ReentrantLock
支持公平锁和非公平锁。公平锁意味着锁的获取顺序按照请求顺序进行,避免线程饥饿。默认情况下,ReentrantLock
是非公平的,但可以通过构造函数创建公平锁:
ReentrantLock fairLock = new ReentrantLock(true);
尝试获取锁
使用 tryLock()
方法可以在不阻塞的情况下尝试获取锁,如果获取失败则立即返回 false
:
if (lock.tryLock()) {
try {
// do something
} finally {
lock.unlock();
}
} else {
// lock not acquired
}
超时获取锁
使用 tryLock(long timeout, TimeUnit unit)
方法可以在指定时间内尝试获取锁,如果超时则返回 false
:
import java.util.concurrent.TimeUnit;
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// do something
} finally {
lock.unlock();
}
} else {
// lock not acquired
}
可中断锁
使用 lockInterruptibly()
方法可以在获取锁时响应中断,如果线程在等待获取锁时被中断,将抛出 InterruptedException
:
try {
lock.lockInterruptibly();
try {
// do something
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// handle interruption
}
应用场景
计数器
通过 ReentrantLock
实现线程安全的计数器:
public class Counter {
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();
}
}
}
生产者-消费者模式
通过 ReentrantLock
和 Condition
实现生产者-消费者模式:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 10;
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
queue.add(value);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int value = queue.poll();
notFull.signal();
return value;
} finally {
lock.unlock();
}
}
}
在上述代码中,我们使用 Condition
来实现队列的等待和通知机制。
注意事项
死锁
在使用 ReentrantLock
时需要注意避免死锁,例如避免循环等待:
public class DeadlockExample {
private final ReentrantLock lock1 = new ReentrantLock();
private final ReentrantLock lock2 = new ReentrantLock();
public void method1() {
lock1.lock();
try {
lock2.lock();
try {
// do something
} finally {
lock2.unlock();
}
} finally {
lock1.unlock();
}
}
public void method2() {
lock2.lock();
try {
lock1.lock();
try {
// do something
} finally {
lock1.unlock();
}
} finally {
lock2.unlock();
}
}
}
在上述代码中,如果 method1
和 method2
分别由不同的线程调用,可能会导致死锁。
结论
ReentrantLock
是 Java 并发编程中一个重要且强大的工具,相比于 synchronized
关键字,提供了更多的灵活性和控制力。通过合理使用 ReentrantLock
,可以有效提高多线程程序的性能和稳定性。然而,在使用时需要注意避免死锁,并合理选择公平锁或非公平锁。
希望本文能帮助你理解 ReentrantLock
的工作原理及其使用方法。如果你有任何问题或建议,欢迎留言讨论。