synchronized 能完成大部分互斥操作。如果需要等待锁时响应中断、设置超时时间,或者区分多个等待条件,可以使用 ReentrantLock。
获取和释放
最基本的写法是先 lock(),然后在 try 中操作共享数据,在 finally 中 unlock()。获取失败时不能释放没有拿到的锁。
lockInterruptibly() 允许在等待获取锁时响应中断;tryLock() 立即返回是否成功,带时间参数的版本则等待一段时间。它们适合不同的失败处理方式,不是单纯的方法名称区别。
默认创建的是非公平锁,new ReentrantLock(true) 使用公平策略。不过不带超时的 tryLock() 即使在公平锁上也允许抢占,不能认为所有入口都严格排队。
用 Condition 等待条件
下面的盒子可以存放一个值,空时消费者等待,满时生产者等待:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionBoxDemo {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private Integer value;
public void put(int next) throws InterruptedException {
lock.lockInterruptibly();
try {
while (value != null) notFull.await();
value = next;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int take() throws InterruptedException {
lock.lockInterruptibly();
try {
while (value == null) notEmpty.await();
int result = value;
value = null;
notFull.signal();
return result;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionBoxDemo box = new ConditionBoxDemo();
Thread consumer = new Thread(() -> {
try {
System.out.println(box.take());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
consumer.start();
box.put(42);
consumer.join();
}
}输出 42。两个 Condition 分别等待“有数据”和“有空位”,都关联同一把锁。
await() 会释放锁,返回前重新获取。signal() 把等待者转入后续的锁竞争,不意味着它立即开始执行,也不会替当前线程释放锁。
这里仍然必须用 while 检查条件。一方面要处理虚假唤醒,另一方面,线程真正重新取得锁时,其他线程可能已经先一步消费数据。
重入次数也要对应
同一线程可以重复 lock(),每次成功获取都要有一次对应的 unlock()。只释放一次,锁仍然可能被自己持有。
ReentrantLock 需要手动释放,所以这里的 finally 不能省略。如果只需要普通互斥,synchronized 的写法会简单一些。