MST

星途 面试题库

面试题:深入剖析Java锁机制的优化策略及死锁预防

在Java并发编程中,随着并发量的增加,锁机制可能会成为性能瓶颈。请详细阐述目前Java锁机制的常见优化策略,比如锁粗化、锁消除、偏向锁、轻量级锁等的原理及实现方式。同时,谈谈在复杂的多线程环境下,如何从设计和编码层面有效地预防死锁的发生,并给出具体的实现思路和代码示例。
20.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

Java锁机制常见优化策略

  1. 锁粗化(Lock Coarsening)
    • 原理:将多次连续的锁操作合并为一次锁操作。在一些情况下,代码中会频繁地加锁和解锁,例如在一个循环中每次迭代都进行加锁解锁操作。锁粗化就是把这些锁操作的范围扩大,减少不必要的加锁解锁次数,从而提高性能。
    • 实现方式:Java虚拟机(JVM)的即时编译器(JIT)在编译时,会对代码进行分析。如果发现相邻的同步块使用的是同一个锁对象,JIT会将这几个同步块合并成一个大的同步块,锁的范围覆盖了原来多个同步块的范围。
  2. 锁消除(Lock Elimination)
    • 原理:JVM在编译时,通过对运行上下文的扫描,去除不可能存在竞争的锁。如果JVM检测到一段代码中的锁对象只在当前线程内使用,不存在多线程竞争的情况,那么JVM会在编译时将这些锁操作消除,避免了不必要的锁开销。
    • 实现方式:JIT编译器在编译阶段,通过逃逸分析技术来判断对象的作用域。如果一个对象的引用不会逃逸出当前方法或线程,那么针对该对象的锁操作就可以被消除。例如,局部变量对象的锁操作,如果该对象没有被外部线程访问到,就可以进行锁消除。
  3. 偏向锁(Biased Locking)
    • 原理:偏向锁是为了在无竞争情况下减少锁获取的开销而设计的。它假设在大多数情况下,锁总是被同一个线程获取。当一个线程首次获取锁时,会在对象头中记录下当前线程的ID,以后该线程再次获取锁时,只需检查对象头中的线程ID是否与自己的ID一致,如果一致则无需进行额外的同步操作,从而减少了锁获取的开销。
    • 实现方式:当一个对象第一次被线程获取锁时,JVM会在对象头中设置偏向锁标志位,并记录下当前线程的ID。后续该线程再次获取锁时,通过对比对象头中的线程ID和当前线程ID来判断是否可以直接获取锁。如果不一致,才会进行锁升级等操作。当有其他线程尝试获取该锁时,偏向锁会被撤销,升级为轻量级锁。
  4. 轻量级锁(Lightweight Locking)
    • 原理:轻量级锁是在偏向锁失效,且线程竞争不激烈的情况下使用的。它的核心思想是在没有竞争的情况下,通过CAS(Compare - And - Swap)操作尝试将锁对象的对象头中的Mark Word替换为指向当前线程栈帧中锁记录的指针,从而避免重量级锁使用操作系统互斥量带来的高开销。
    • 实现方式:当线程进入同步块时,会在自己的栈帧中创建一个锁记录(Lock Record),并将锁对象的Mark Word复制到锁记录中,称为Displaced Mark Word。然后线程尝试使用CAS操作将对象头中的Mark Word替换为指向自己栈帧中锁记录的指针。如果替换成功,当前线程就获得了轻量级锁;如果替换失败,说明存在竞争,轻量级锁会膨胀为重量级锁。

预防死锁的设计和编码策略

  1. 避免循环依赖
    • 实现思路:在设计多线程系统时,要避免线程之间形成资源获取的循环依赖。例如,在一个系统中有多个资源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();
    }
}
  1. 使用定时锁
    • 实现思路:使用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();
    }
}
  1. 资源分配图算法
    • 实现思路:在系统运行过程中,维护一个资源分配图,记录线程和资源之间的分配关系。通过算法检测资源分配图中是否存在环,如果存在环则表示可能发生死锁,此时可以通过撤销某些线程的资源分配来打破环,从而避免死锁。
    • 代码示例:实现资源分配图算法相对复杂,涉及图数据结构的操作。以下是一个简单的检测有向图是否存在环的示例代码(假设已经有表示资源分配图的数据结构)。
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");
        }
    }
}

在实际应用中,需要根据具体的资源分配情况构建资源分配图,并结合系统逻辑进行死锁预防和处理。