MST

星途 面试题库

面试题:Java多线程I/O中的死锁分析与解决

假设在一个Java多线程程序中,有两个线程T1和T2,T1持有资源A(如一个文件锁)并尝试获取资源B(另一个文件锁),T2持有资源B并尝试获取资源A,这可能导致死锁。如果资源A和B分别对应两个不同的`RandomAccessFile`实例,用于多线程I/O操作,你如何检测和避免这种死锁情况?请从代码实现层面详细说明。
12.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

检测死锁

  1. 使用JVM自带工具
    • jstack命令:在程序运行时,可以通过jstack <pid>命令获取Java进程的线程堆栈信息。<pid>是Java进程的ID,可以通过jps命令获取。如果程序发生死锁,jstack输出的信息中会明确指出死锁相关的线程及它们等待的资源。例如:
      jps -l
      # 找到目标Java程序的进程ID
      jstack <pid>
      
  2. 使用Java Management Extensions (JMX)
    • 通过JMX可以实时监控Java应用程序的运行状态。可以自定义一个MBean来监控线程的状态和资源持有情况。在代码中,可以使用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) {
            new Thread(() -> {
                try {
                    Thread.sleep(1000);
                    long[] deadlockedThreads = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
                    if (deadlockedThreads != null) {
                        System.out.println("Detected deadlock:");
                        for (long threadId : deadlockedThreads) {
                            ThreadInfo threadInfo = ManagementFactory.getThreadMXBean().getThreadInfo(threadId);
                            System.out.println("Thread: " + threadInfo.getThreadName());
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    
    • 上述代码启动一个线程,定期检查是否有死锁发生。如果有死锁,会打印出死锁线程的名称。

避免死锁

  1. 按照固定顺序获取锁
    • 定义一个固定的顺序来获取资源A和B对应的锁。例如,总是先获取RandomAccessFile A的锁,再获取RandomAccessFile B的锁。示例代码如下:
    import java.io.RandomAccessFile;
    
    public class FileLockExample {
        private static final Object lockA = new Object();
        private static final Object lockB = new Object();
    
        public static void main(String[] args) {
            RandomAccessFile fileA = null;
            RandomAccessFile fileB = null;
            try {
                fileA = new RandomAccessFile("fileA.txt", "rw");
                fileB = new RandomAccessFile("fileB.txt", "rw");
                Thread T1 = new Thread(() -> {
                    synchronized (lockA) {
                        System.out.println("T1 acquired lockA");
                        synchronized (lockB) {
                            System.out.println("T1 acquired lockB");
                            // 进行I/O操作
                            try {
                                fileA.writeUTF("Data from T1");
                                fileB.writeUTF("Data from T1");
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
                Thread T2 = new Thread(() -> {
                    synchronized (lockA) {
                        System.out.println("T2 acquired lockA");
                        synchronized (lockB) {
                            System.out.println("T2 acquired lockB");
                            // 进行I/O操作
                            try {
                                fileA.writeUTF("Data from T2");
                                fileB.writeUTF("Data from T2");
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
                T1.start();
                T2.start();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fileA != null) fileA.close();
                    if (fileB != null) fileB.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 在上述代码中,T1T2线程都按照先获取lockA,再获取lockB的顺序获取锁,从而避免死锁。
  2. 使用tryLock方法
    • java.util.concurrent.locks.Lock接口提供了tryLock方法,该方法尝试获取锁,如果获取成功则返回true,否则返回false。可以利用这个方法在获取锁时设置一个超时时间,避免无限等待。示例代码如下:
    import java.io.RandomAccessFile;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TryLockExample {
        private static final Lock lockA = new ReentrantLock();
        private static final Lock lockB = new ReentrantLock();
    
        public static void main(String[] args) {
            RandomAccessFile fileA = null;
            RandomAccessFile fileB = null;
            try {
                fileA = new RandomAccessFile("fileA.txt", "rw");
                fileB = new RandomAccessFile("fileB.txt", "rw");
                Thread T1 = new Thread(() -> {
                    if (lockA.tryLock()) {
                        try {
                            System.out.println("T1 acquired lockA");
                            if (lockB.tryLock()) {
                                try {
                                    System.out.println("T1 acquired lockB");
                                    // 进行I/O操作
                                    fileA.writeUTF("Data from T1");
                                    fileB.writeUTF("Data from T1");
                                } catch (Exception e) {
                                    e.printStackTrace();
                                } finally {
                                    lockB.unlock();
                                }
                            } else {
                                System.out.println("T1 could not acquire lockB");
                            }
                        } finally {
                            lockA.unlock();
                        }
                    } else {
                        System.out.println("T1 could not acquire lockA");
                    }
                });
                Thread T2 = new Thread(() -> {
                    if (lockA.tryLock()) {
                        try {
                            System.out.println("T2 acquired lockA");
                            if (lockB.tryLock()) {
                                try {
                                    System.out.println("T2 acquired lockB");
                                    // 进行I/O操作
                                    fileA.writeUTF("Data from T2");
                                    fileB.writeUTF("Data from T2");
                                } catch (Exception e) {
                                    e.printStackTrace();
                                } finally {
                                    lockB.unlock();
                                }
                            } else {
                                System.out.println("T2 could not acquire lockB");
                            }
                        } finally {
                            lockA.unlock();
                        }
                    } else {
                        System.out.println("T2 could not acquire lockA");
                    }
                });
                T1.start();
                T2.start();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fileA != null) fileA.close();
                    if (fileB != null) fileB.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 在上述代码中,T1T2线程通过tryLock方法尝试获取锁,如果获取失败则不会无限等待,从而避免死锁。还可以使用tryLock(long timeout, TimeUnit unit)方法设置超时时间,在超时后放弃获取锁。