MST
星途 面试题库

面试题:Java中可重入锁在多线程环境下如何避免死锁

请描述在Java中使用可重入锁(如ReentrantLock)时,如何在多线程场景下避免死锁问题。结合实际代码示例,说明死锁产生的原因以及如何通过合理的锁使用策略来预防死锁。
43.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

死锁产生的原因

死锁通常发生在多个线程相互等待对方释放锁的情况下。例如,假设有两个线程 Thread1Thread2,以及两把锁 lock1lock2Thread1 获得了 lock1,然后尝试获取 lock2,而此时 Thread2 获得了 lock2,又尝试获取 lock1,这样两个线程就会相互等待,造成死锁。

示例代码(死锁场景)

import java.util.concurrent.locks.ReentrantLock;

public class DeadlockExample {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            lock1.lock();
            try {
                System.out.println("Thread1 acquired lock1");
                Thread.sleep(100);
                lock2.lock();
                try {
                    System.out.println("Thread1 acquired lock2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock2.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock1.unlock();
            }
        });

        Thread thread2 = new Thread(() -> {
            lock2.lock();
            try {
                System.out.println("Thread2 acquired lock2");
                Thread.sleep(100);
                lock1.lock();
                try {
                    System.out.println("Thread2 acquired lock1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock1.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock2.unlock();
            }
        });

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

预防死锁的策略

  1. 按照固定顺序获取锁:所有线程按照相同的顺序获取锁,这样可以避免相互等待。
  2. 使用定时锁:尝试获取锁时设置一个超时时间,如果在超时时间内没有获取到锁,则放弃获取并进行其他操作。

示例代码(预防死锁 - 固定顺序获取锁)

import java.util.concurrent.locks.ReentrantLock;

public class DeadlockPreventionExample {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            // 始终先获取 lock1,再获取 lock2
            lock1.lock();
            try {
                System.out.println("Thread1 acquired lock1");
                Thread.sleep(100);
                lock2.lock();
                try {
                    System.out.println("Thread1 acquired lock2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock2.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock1.unlock();
            }
        });

        Thread thread2 = new Thread(() -> {
            // 同样先获取 lock1,再获取 lock2
            lock1.lock();
            try {
                System.out.println("Thread2 acquired lock1");
                Thread.sleep(100);
                lock2.lock();
                try {
                    System.out.println("Thread2 acquired lock2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock2.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock1.unlock();
            }
        });

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

示例代码(预防死锁 - 使用定时锁)

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockPreventionWithTimeoutExample {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            boolean gotLock1 = false;
            boolean gotLock2 = false;
            try {
                gotLock1 = lock1.tryLock(100, TimeUnit.MILLISECONDS);
                if (gotLock1) {
                    System.out.println("Thread1 acquired lock1");
                    gotLock2 = lock2.tryLock(100, TimeUnit.MILLISECONDS);
                    if (gotLock2) {
                        System.out.println("Thread1 acquired lock2");
                    } else {
                        System.out.println("Thread1 could not acquire lock2, releasing lock1");
                    }
                } else {
                    System.out.println("Thread1 could not acquire lock1");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (gotLock2) {
                    lock2.unlock();
                }
                if (gotLock1) {
                    lock1.unlock();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            boolean gotLock1 = false;
            boolean gotLock2 = false;
            try {
                gotLock2 = lock2.tryLock(100, TimeUnit.MILLISECONDS);
                if (gotLock2) {
                    System.out.println("Thread2 acquired lock2");
                    gotLock1 = lock1.tryLock(100, TimeUnit.MILLISECONDS);
                    if (gotLock1) {
                        System.out.println("Thread2 acquired lock1");
                    } else {
                        System.out.println("Thread2 could not acquire lock1, releasing lock2");
                    }
                } else {
                    System.out.println("Thread2 could not acquire lock2");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (gotLock1) {
                    lock1.unlock();
                }
                if (gotLock2) {
                    lock2.unlock();
                }
            }
        });

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

通过以上策略,可以有效地避免在使用可重入锁时多线程场景下的死锁问题。