如果有遗漏,评论区告诉我进行补充
面试官: Java Concurrency API中的Lock接口是什么?对比synchronized它有什么优势?
我回答:
Java Concurrency API 中的 Lock
接口是 java.util.concurrent.locks
包的一部分,它提供了一种比传统的 synchronized
关键字更为灵活和强大的锁定机制。Lock
接口允许更细粒度的锁控制,支持尝试非阻塞地获取锁、可中断的锁获取操作、支持超时, 提供了更丰富的功能,同时也增强了线程安全性和性能。
Lock 接口的优势
-
非阻塞性等待:
Lock
接口中提供了tryLock()
方法,可以让线程尝试获取锁,如果锁不可用,线程可以选择立即返回而不是阻塞等待。这在某些场景下可以避免线程阻塞,提高响应速度。tryLock(long timeout, TimeUnit unit)
方法允许线程在指定的时间内尝试获取锁,超过时间后仍未获取到锁则返回,这可以避免长时间的阻塞。
-
可中断的等待:
- 当线程持有锁时,其他等待获取锁的线程可以通过中断的方式被提前唤醒。
Lock
接口的lockInterruptibly()
方法允许线程在等待锁的过程中被中断,这在需要响应外部中断信号的场景中非常有用。 - synchronized在获取锁时是不可中断的,这意味着线程在获取锁的过程中无法响应中断(InterruptedException)。
- 当线程持有锁时,其他等待获取锁的线程可以通过中断的方式被提前唤醒。
-
公平性:
Lock
接口允许创建公平锁和非公平锁。公平锁保证线程按照请求锁的顺序获取锁,这可以避免饥饿问题。非公平锁则不保证顺序,可能优先给先到达的线程或最近释放锁的线程分配锁,这在某些场景下可以提高性能。- 相比之下,synchronized块默认采用非公平锁策略。
-
可重入性:
ReentrantLock
类实现了Lock
接口,它提供了可重入锁的功能,允许同一个线程多次获取同一个锁,而不会产生死锁。每次调用lock()
方法都会增加锁的计数,对应的需要调用相同次数的unlock()
方法来释放锁。
-
条件变量:
Lock
接口的实现通常提供了Condition
对象,这比wait()
和notify()
方法提供了更细粒度的控制。Condition
允许线程在等待特定条件满足时释放锁,当条件满足时,线程可以重新获取锁并继续执行,这可以避免不必要的锁竞争。- 相比之下,synchronized关键字只能与Object的wait()、notify()和notifyAll()方法配合使用,这些方法都是基于单个Condition对象的。
-
显式解锁:
Lock
接口要求线程显式地调用unlock()
方法来释放锁,这使得锁的管理更加清晰,避免了由于异常导致的锁无法释放的问题。通常建议在finally
块中调用unlock()
方法,以确保锁总是被释放。
-
支持超时:
- Lock接口提供了tryLock(long time, TimeUnit unit)方法,允许线程在给定的等待时间内尝试获取锁。如果在指定时间内没有获取到锁,线程可以放弃等待并继续执行后续操作,这增加了程序的健壮性和灵活性。
-
更好的性能:
- 在某些复杂的同步需求和高竞争环境下,Lock接口可能提供比synchronized更好的性能。这主要是因为Lock接口的实现通常更加复杂和灵活,能够利用更多的并发控制机制(如CAS、AQS等)来优化性能。
示例代码
下面是一个使用 ReentrantLock
的示例:
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
public void someMethod() {
lock.lock();
try {
// 执行临界区代码
System.out.println(Thread.currentThread().getName() + " executing critical section");
} finally {
lock.unlock();
}
}
}
与 synchronized 的比较
synchronized
关键字提供了自动的锁管理,当线程进入同步代码块或方法时自动获取锁,离开时自动释放锁。然而,synchronized
的缺点是缺乏灵活性,不支持非阻塞性等待、中断等待、条件变量等功能,而且锁的获取和释放是由JVM自动管理的,这在某些情况下可能导致性能问题或无法满足特定的线程控制需求。使用Lock接口时更加小心谨慎地管理锁的生命周期和状态以避免死锁和资源泄漏等问题。
相比之下,Lock
接口提供了更精细的锁控制,允许程序员根据具体的应用场景选择最适合的锁策略,从而优化程序的性能和响应性。