MST

星途 面试题库

面试题:Java多线程下的死锁场景分析与解决

请描述一个可能会在Java多线程程序中出现死锁的场景,并说明如何通过代码示例去模拟该场景,以及阐述死锁的检测和解决方法。
22.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

可能出现死锁的场景

在Java多线程程序中,当两个或多个线程相互等待对方释放资源,而这些资源又被对方持有,就会出现死锁。例如,线程A持有资源1并等待资源2,而线程B持有资源2并等待资源1,这样就形成了死锁。

代码示例模拟死锁场景

public class DeadlockExample {
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: Holding resource 1");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource2) {
                    System.out.println("Thread 1: Holding resource 1 & 2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2: Holding resource 2");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (resource1) {
                    System.out.println("Thread 2: Holding resource 1 & 2");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

死锁的检测方法

  1. 使用jstack命令:在运行上述程序后,可以通过jstack <pid>命令(<pid>为Java进程ID,可以通过jps命令获取)来查看线程的堆栈信息,其中会指出死锁相关的线程。
  2. 使用JConsole或VisualVM:这些工具是Java自带的监控工具,可以连接到运行的Java程序,在“线程”标签页中查看死锁情况,它们会直观地显示死锁的线程及相关信息。

死锁的解决方法

  1. 避免嵌套锁:尽量避免在一个线程中同时获取多个锁,若必须获取多个锁,确保所有线程以相同的顺序获取锁。例如上述代码中,可以让两个线程都先获取resource1,再获取resource2
  2. 使用定时锁:使用tryLock方法代替synchronized关键字,该方法尝试获取锁,如果在指定时间内无法获取则返回false,这样线程可以释放已获取的锁并重新尝试或执行其他操作,避免死锁。示例如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class AvoidDeadlockExample {
    private static final Lock resource1 = new ReentrantLock();
    private static final Lock resource2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            boolean gotResource1 = false;
            boolean gotResource2 = false;
            try {
                gotResource1 = resource1.tryLock(100, java.util.concurrent.TimeUnit.MILLISECONDS);
                if (gotResource1) {
                    System.out.println("Thread 1: Holding resource 1");
                    gotResource2 = resource2.tryLock(100, java.util.concurrent.TimeUnit.MILLISECONDS);
                    if (gotResource2) {
                        System.out.println("Thread 1: Holding resource 1 & 2");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (gotResource2) {
                    resource2.unlock();
                }
                if (gotResource1) {
                    resource1.unlock();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            boolean gotResource1 = false;
            boolean gotResource2 = false;
            try {
                gotResource2 = resource2.tryLock(100, java.util.concurrent.TimeUnit.MILLISECONDS);
                if (gotResource2) {
                    System.out.println("Thread 2: Holding resource 2");
                    gotResource1 = resource1.tryLock(100, java.util.concurrent.TimeUnit.MILLISECONDS);
                    if (gotResource1) {
                        System.out.println("Thread 2: Holding resource 1 & 2");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (gotResource1) {
                    resource1.unlock();
                }
                if (gotResource2) {
                    resource2.unlock();
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}
  1. 死锁检测与恢复:使用死锁检测机制(如上述的jstack、JConsole等),在检测到死锁后,通过外部干预(如重启相关服务或进程)来解决死锁问题。