面试题答案
一键面试检测死锁
- 使用JVM自带工具:
- jstack命令:在程序运行时,可以通过
jstack <pid>
命令获取Java进程的线程堆栈信息。<pid>
是Java进程的ID,可以通过jps
命令获取。如果程序发生死锁,jstack
输出的信息中会明确指出死锁相关的线程及它们等待的资源。例如:jps -l # 找到目标Java程序的进程ID jstack <pid>
- jstack命令:在程序运行时,可以通过
- 使用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(); } }
- 上述代码启动一个线程,定期检查是否有死锁发生。如果有死锁,会打印出死锁线程的名称。
- 通过JMX可以实时监控Java应用程序的运行状态。可以自定义一个MBean来监控线程的状态和资源持有情况。在代码中,可以使用
避免死锁
- 按照固定顺序获取锁:
- 定义一个固定的顺序来获取资源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(); } } } }
- 在上述代码中,
T1
和T2
线程都按照先获取lockA
,再获取lockB
的顺序获取锁,从而避免死锁。
- 定义一个固定的顺序来获取资源A和B对应的锁。例如,总是先获取
- 使用
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(); } } } }
- 在上述代码中,
T1
和T2
线程通过tryLock
方法尝试获取锁,如果获取失败则不会无限等待,从而避免死锁。还可以使用tryLock(long timeout, TimeUnit unit)
方法设置超时时间,在超时后放弃获取锁。