面试题答案
一键面试锁粒度选择与性能和死锁风险的平衡
- 锁粒度对性能的影响
- 粗粒度锁:
- 定义:粗粒度锁是指在较大范围的代码块或数据上使用锁。例如,对整个类的所有方法都使用同一个锁。
- 优点:实现简单,在并发量较低时,能有效避免竞争,保证数据一致性。
- 缺点:当并发量增加时,由于锁的范围大,可能导致很多线程等待锁,从而降低系统的并发性能。
- 细粒度锁:
- 定义:细粒度锁是将锁应用于较小的代码块或数据单元。比如,对类中的不同方法或不同数据成员分别使用不同的锁。
- 优点:能提高系统的并发性能,因为不同线程可以同时访问不同的锁保护的资源。
- 缺点:实现相对复杂,增加了锁的管理成本,并且可能由于锁的数量增多导致死锁风险上升。
- 粗粒度锁:
- 锁粒度对死锁风险的影响
- 粗粒度锁:
- 死锁风险:死锁风险相对较低,因为只有一把锁,不存在多个锁相互等待的情况。
- 细粒度锁:
- 死锁风险:死锁风险相对较高,因为多个线程可能同时获取不同的锁,并试图获取对方已持有的锁,从而形成死锁。
- 粗粒度锁:
- 代码示例及分析
- 粗粒度锁示例:
public class CoarseGrainedLockExample {
private static final Object lock = new Object();
public void method1() {
synchronized (lock) {
// 模拟业务操作
for (int i = 0; i < 1000000; i++) {
// 空操作
}
}
}
public void method2() {
synchronized (lock) {
// 模拟业务操作
for (int i = 0; i < 1000000; i++) {
// 空操作
}
}
}
}
- **分析**:在这个例子中,`method1` 和 `method2` 都使用同一个锁 `lock`。如果有多个线程同时调用这两个方法,只有一个线程能获得锁并执行,其他线程必须等待。这在高并发场景下会严重影响性能。但由于只有一把锁,不会出现死锁。
- **细粒度锁示例**:
public class FineGrainedLockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
// 模拟业务操作
for (int i = 0; i < 1000000; i++) {
// 空操作
}
synchronized (lock2) {
// 模拟业务操作
for (int i = 0; i < 1000000; i++) {
// 空操作
}
}
}
}
public void method2() {
synchronized (lock2) {
// 模拟业务操作
for (int i = 0; i < 1000000; i++) {
// 空操作
}
synchronized (lock1) {
// 模拟业务操作
for (int i = 0; i < 1000000; i++) {
// 空操作
}
}
}
}
}
- **分析**:在这个例子中,`method1` 和 `method2` 使用了不同的锁 `lock1` 和 `lock2`。这使得不同线程可以同时访问 `method1` 和 `method2` 中被不同锁保护的部分,提高了并发性能。然而,如果线程 A 调用 `method1` 获得 `lock1` 后,试图获取 `lock2`,而线程 B 调用 `method2` 获得 `lock2` 后,试图获取 `lock1`,就会形成死锁。
4. 预防和解决措施
- 死锁预防措施:
- 破坏死锁的四个必要条件:
- 互斥条件:一般不能破坏,因为锁本身就是为了保证互斥访问。
- 占有并等待条件:可以通过一次性获取所有需要的锁来避免。例如,在上述细粒度锁示例中,可以将 method1
和 method2
改为先获取 lock1
,再获取 lock2
,这样就不会出现死锁。
- 不可剥夺条件:在某些情况下,可以通过设置锁的超时时间,当一个线程获取锁超时后,释放已持有的锁,从而破坏不可剥夺条件。
- 循环等待条件:可以对锁进行排序,线程按照固定顺序获取锁,避免循环等待。
- 死锁检测与解决:
- 死锁检测:可以使用 ThreadMXBean
来检测死锁。例如:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class DeadlockDetector {
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
for (long threadId : deadlockedThreads) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);
System.out.println("Deadlocked thread: " + threadInfo.getThreadName());
}
}
}
}
- **死锁解决**:一旦检测到死锁,可以通过重启相关线程或进程来解决。在更复杂的系统中,可以通过设计补偿机制,让相关业务回滚,然后重新执行。
在高并发的Java应用开发中,应根据实际业务场景,合理选择锁粒度,在提高系统性能的同时,有效预防和处理死锁问题。