面试题答案
一键面试Java锁机制常见优化策略
- 锁粗化(Lock Coarsening)
- 原理:将多次连续的锁操作合并为一次锁操作。在一些情况下,代码中会频繁地加锁和解锁,例如在一个循环中每次迭代都进行加锁解锁操作。锁粗化就是把这些锁操作的范围扩大,减少不必要的加锁解锁次数,从而提高性能。
- 实现方式:Java虚拟机(JVM)的即时编译器(JIT)在编译时,会对代码进行分析。如果发现相邻的同步块使用的是同一个锁对象,JIT会将这几个同步块合并成一个大的同步块,锁的范围覆盖了原来多个同步块的范围。
- 锁消除(Lock Elimination)
- 原理:JVM在编译时,通过对运行上下文的扫描,去除不可能存在竞争的锁。如果JVM检测到一段代码中的锁对象只在当前线程内使用,不存在多线程竞争的情况,那么JVM会在编译时将这些锁操作消除,避免了不必要的锁开销。
- 实现方式:JIT编译器在编译阶段,通过逃逸分析技术来判断对象的作用域。如果一个对象的引用不会逃逸出当前方法或线程,那么针对该对象的锁操作就可以被消除。例如,局部变量对象的锁操作,如果该对象没有被外部线程访问到,就可以进行锁消除。
- 偏向锁(Biased Locking)
- 原理:偏向锁是为了在无竞争情况下减少锁获取的开销而设计的。它假设在大多数情况下,锁总是被同一个线程获取。当一个线程首次获取锁时,会在对象头中记录下当前线程的ID,以后该线程再次获取锁时,只需检查对象头中的线程ID是否与自己的ID一致,如果一致则无需进行额外的同步操作,从而减少了锁获取的开销。
- 实现方式:当一个对象第一次被线程获取锁时,JVM会在对象头中设置偏向锁标志位,并记录下当前线程的ID。后续该线程再次获取锁时,通过对比对象头中的线程ID和当前线程ID来判断是否可以直接获取锁。如果不一致,才会进行锁升级等操作。当有其他线程尝试获取该锁时,偏向锁会被撤销,升级为轻量级锁。
- 轻量级锁(Lightweight Locking)
- 原理:轻量级锁是在偏向锁失效,且线程竞争不激烈的情况下使用的。它的核心思想是在没有竞争的情况下,通过CAS(Compare - And - Swap)操作尝试将锁对象的对象头中的Mark Word替换为指向当前线程栈帧中锁记录的指针,从而避免重量级锁使用操作系统互斥量带来的高开销。
- 实现方式:当线程进入同步块时,会在自己的栈帧中创建一个锁记录(Lock Record),并将锁对象的Mark Word复制到锁记录中,称为Displaced Mark Word。然后线程尝试使用CAS操作将对象头中的Mark Word替换为指向自己栈帧中锁记录的指针。如果替换成功,当前线程就获得了轻量级锁;如果替换失败,说明存在竞争,轻量级锁会膨胀为重量级锁。
预防死锁的设计和编码策略
- 避免循环依赖
- 实现思路:在设计多线程系统时,要避免线程之间形成资源获取的循环依赖。例如,在一个系统中有多个资源A、B、C,线程1获取资源A后尝试获取资源B,线程2获取资源B后尝试获取资源C,线程3获取资源C后尝试获取资源A,这样就形成了循环依赖,可能导致死锁。要打破这种循环依赖,可以重新设计资源获取的顺序,确保所有线程按照相同的顺序获取资源。
- 代码示例
// 资源类
class Resource {
private String name;
public Resource(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 线程类
class Thread1 extends Thread {
private Resource resourceA;
private Resource resourceB;
public Thread1(Resource resourceA, Resource resourceB) {
this.resourceA = resourceA;
this.resourceB = resourceB;
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println("Thread1 acquired resourceA");
// 按照固定顺序获取资源
synchronized (resourceB) {
System.out.println("Thread1 acquired resourceB");
}
}
}
}
class Thread2 extends Thread {
private Resource resourceA;
private Resource resourceB;
public Thread2(Resource resourceA, Resource resourceB) {
this.resourceA = resourceA;
this.resourceB = resourceB;
}
@Override
public void run() {
// 与Thread1按照相同顺序获取资源
synchronized (resourceA) {
System.out.println("Thread2 acquired resourceA");
synchronized (resourceB) {
System.out.println("Thread2 acquired resourceB");
}
}
}
}
public class DeadlockPrevention {
public static void main(String[] args) {
Resource resourceA = new Resource("A");
Resource resourceB = new Resource("B");
Thread1 thread1 = new Thread1(resourceA, resourceB);
Thread2 thread2 = new Thread2(resourceA, resourceB);
thread1.start();
thread2.start();
}
}
- 使用定时锁
- 实现思路:使用
tryLock
方法并设置超时时间。当线程尝试获取锁时,如果在指定时间内没有获取到锁,线程可以放弃获取锁并进行其他操作,避免线程无限期等待,从而防止死锁。 - 代码示例
- 实现思路:使用
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ThreadWithTryLock extends Thread {
private Lock lock1;
private Lock lock2;
public ThreadWithTryLock(Lock lock1, Lock lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
boolean gotLock1 = false;
boolean gotLock2 = false;
try {
gotLock1 = lock1.tryLock(1, java.util.concurrent.TimeUnit.SECONDS);
if (gotLock1) {
System.out.println("Thread got lock1");
gotLock2 = lock2.tryLock(1, java.util.concurrent.TimeUnit.SECONDS);
if (gotLock2) {
System.out.println("Thread got lock2");
} else {
System.out.println("Thread couldn't get lock2, releasing lock1");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (gotLock1) {
lock1.unlock();
}
if (gotLock2) {
lock2.unlock();
}
}
}
}
public class TryLockExample {
public static void main(String[] args) {
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
ThreadWithTryLock thread1 = new ThreadWithTryLock(lock1, lock2);
ThreadWithTryLock thread2 = new ThreadWithTryLock(lock2, lock1);
thread1.start();
thread2.start();
}
}
- 资源分配图算法
- 实现思路:在系统运行过程中,维护一个资源分配图,记录线程和资源之间的分配关系。通过算法检测资源分配图中是否存在环,如果存在环则表示可能发生死锁,此时可以通过撤销某些线程的资源分配来打破环,从而避免死锁。
- 代码示例:实现资源分配图算法相对复杂,涉及图数据结构的操作。以下是一个简单的检测有向图是否存在环的示例代码(假设已经有表示资源分配图的数据结构)。
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
class Graph {
private int vertices;
private List<List<Integer>> adj;
public Graph(int vertices) {
this.vertices = vertices;
adj = new ArrayList<>(vertices);
for (int i = 0; i < vertices; i++) {
adj.add(new ArrayList<>());
}
}
public void addEdge(int v, int w) {
adj.get(v).add(w);
}
public boolean isCyclic() {
boolean[] visited = new boolean[vertices];
boolean[] recursionStack = new boolean[vertices];
for (int i = 0; i < vertices; i++) {
if (!visited[i]) {
if (isCyclicUtil(i, visited, recursionStack)) {
return true;
}
}
}
return false;
}
private boolean isCyclicUtil(int v, boolean[] visited, boolean[] recursionStack) {
visited[v] = true;
recursionStack[v] = true;
List<Integer> children = adj.get(v);
for (int child : children) {
if (!visited[child] && isCyclicUtil(child, visited, recursionStack)) {
return true;
} else if (recursionStack[child]) {
return true;
}
}
recursionStack[v] = false;
return false;
}
}
public class DeadlockDetection {
public static void main(String[] args) {
Graph graph = new Graph(4);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(1, 2);
graph.addEdge(2, 0);
graph.addEdge(2, 3);
graph.addEdge(3, 3);
if (graph.isCyclic()) {
System.out.println("Graph contains cycle");
} else {
System.out.println("Graph does not contain cycle");
}
}
}
在实际应用中,需要根据具体的资源分配情况构建资源分配图,并结合系统逻辑进行死锁预防和处理。