多线程编程就像管理一支交响乐团,每个乐器都需要在正确的时间发声。Java提供了两种协调线程的方式:传统的synchronized关键字和更灵活的Lock接口。今天我们来聊聊这个让线程协作更优雅的工具。
1.1 Lock接口简介与作用
记得我第一次接触多线程编程时,总是担心数据竞争和线程安全问题。Lock接口的出现让这些问题有了更清晰的解决方案。
Lock接口位于java.util.concurrent.locks包中,它定义了一组比synchronized更丰富的锁操作。想象一下,synchronized像是自动门——进去后自动上锁,出来时自动解锁。而Lock接口更像是手动门锁,你可以更精细地控制何时上锁、何时解锁。
这个设计特别适合复杂的同步场景。比如银行转账操作,可能需要同时锁定两个账户。使用Lock接口,你可以尝试获取两个锁,如果失败就释放已获得的锁,避免死锁。这种灵活性是synchronized难以实现的。
1.2 Lock接口的核心方法解析
Lock接口的核心方法其实很直观。lock()方法用于获取锁,如果锁不可用,当前线程将休眠直到获得锁。unlock()方法释放锁,这个调用必须放在finally块中确保执行。
tryLock()方法是我个人比较喜欢的功能。它尝试获取锁,如果锁可用就立即返回true,否则返回false。这个方法不会让线程无限等待,特别适合实现超时控制。
我曾在项目中遇到过这样的需求:从多个数据源获取数据,但每个数据源都有响应时间限制。使用tryLock(timeout, unit)方法,我可以设置最大等待时间,超时就转向备用方案。这种优雅的降级机制让系统更健壮。
readLock()和writeLock()在读写锁中特别有用。多个线程可以同时持有读锁,但写锁是排他的。这种设计显著提升了读多写少场景的性能。
1.3 Lock接口与synchronized关键字的对比
很多开发者会问:既然有了synchronized,为什么还需要Lock接口?这个问题让我想起手动挡和自动挡汽车的选择。
synchronized是JVM内置的同步机制,使用简单,自动管理锁的获取和释放。但它有些局限性:无法中断正在等待锁的线程,无法设置获取锁的超时时间,必须在获取锁的同一个方法中释放锁。
Lock接口提供了更细粒度的控制。你可以尝试获取锁而不阻塞,可以响应中断,可以设置超时。这些特性在构建高响应性系统时非常宝贵。
不过,synchronized也有它的优势。代码更简洁,不容易出现忘记释放锁的情况。在简单的同步场景中,它仍然是很好的选择。
选择哪个取决于具体需求。如果只是基本的同步,synchronized足够用了。如果需要更高级的功能,比如可中断的锁获取、尝试锁获取、公平性策略,那么Lock接口是更好的选择。
每个工具都有其适用场景,理解它们的差异能帮助我们做出更明智的技术选型。 ReentrantLock lock = new ReentrantLock();
public void outerMethod() {
lock.lock();
try {
innerMethod(); // 这里可以重入获取锁
} finally {
lock.unlock();
}
}
public void innerMethod() {
lock.lock(); // 同一个线程可以再次获取锁
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
ReentrantLock lock = new ReentrantLock(); Condition notEmpty = lock.newCondition(); Condition notFull = lock.newCondition();
// 生产者线程 public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (queue.isFull()) {
notFull.await(); // 等待队列非满
}
queue.enqueue(item);
notEmpty.signal(); // 唤醒等待非空的消费者
} finally {
lock.unlock();
}
}
// 消费者线程
public Object take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 等待队列非空
}
Object item = queue.dequeue();
notFull.signal(); // 唤醒等待非满的生产者
return item;
} finally {
lock.unlock();
}
}
public class BlockingQueue
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final T[] items;
private int putIndex, takeIndex, count;
public BlockingQueue(int capacity) {
items = (T[]) new Object[capacity];
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 队列满时等待
}
items[putIndex] = item;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal(); // 唤醒一个消费者
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 队列空时等待
}
T item = items[takeIndex];
items[takeIndex] = null; // 帮助GC
if (++takeIndex == items.length) takeIndex = 0;
count--;
notFull.signal(); // 唤醒一个生产者
return item;
} finally {
lock.unlock();
}
}
}
public class LockOrdering {
private final Object primaryLock = new Object();
private final Object secondaryLock = new Object();
public void methodA() {
synchronized (primaryLock) {
synchronized (secondaryLock) {
// 操作资源
}
}
}
public void methodB() {
synchronized (primaryLock) {
synchronized (secondaryLock) {
// 操作资源
}
}
}
}