MST

星途 面试题库

面试题:Java Worker线程中使用非重入锁可能引发的死锁场景及避免方法

在Java Worker线程的使用场景下,阐述可能由于非重入锁导致死锁的场景,并且详细说明如何通过锁的获取顺序、超时机制等方式来避免这种死锁情况。
11.6万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

非重入锁导致死锁的场景

  1. 场景描述
    • 假设有两个资源ResourceAResourceB,并且有两把非重入锁LockALockB分别用于保护这两个资源。
    • 有两个Worker线程Thread1Thread2Thread1首先获取LockA,然后尝试获取LockB。而Thread2首先获取LockB,然后尝试获取LockA。如果Thread1获取LockA后,Thread2获取LockB,此时Thread1等待LockBThread2等待LockA,就会形成死锁。
    • 示例代码如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockExample {
    private static final Lock LockA = new ReentrantLock(false);
    private static final Lock LockB = new ReentrantLock(false);

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

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

        Thread1.start();
        Thread2.start();
    }
}
  1. 原理分析
    • 非重入锁意味着如果一个线程已经持有了锁,再次尝试获取该锁时会被阻塞。在上述场景中,两个线程以不同顺序获取锁,且都不释放已持有的锁就尝试获取另一个锁,从而导致互相等待,形成死锁。

避免死锁的方法

  1. 锁的获取顺序
    • 方法描述:所有线程都按照相同的顺序获取锁。例如,在上述场景中,无论是Thread1还是Thread2,都先获取LockA,再获取LockB。这样就不会出现互相等待的情况。
    • 示例代码修改
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class NoDeadlockByOrderExample {
    private static final Lock LockA = new ReentrantLock(false);
    private static final Lock LockB = new ReentrantLock(false);

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

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

        Thread1.start();
        Thread2.start();
    }
}
  1. 超时机制
    • 方法描述:在获取锁时设置一个超时时间。如果在超时时间内没有获取到锁,则放弃获取并释放已获取的锁,从而避免无限等待。在Java中,可以使用tryLock(long timeout, TimeUnit unit)方法。
    • 示例代码修改
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class NoDeadlockByTimeoutExample {
    private static final Lock LockA = new ReentrantLock(false);
    private static final Lock LockB = new ReentrantLock(false);

    public static void main(String[] args) {
        Thread Thread1 = new Thread(() -> {
            boolean gotLockA = false;
            boolean gotLockB = false;
            try {
                gotLockA = LockA.tryLock(1, TimeUnit.SECONDS);
                if (gotLockA) {
                    System.out.println("Thread1 has acquired LockA");
                    gotLockB = LockB.tryLock(1, TimeUnit.SECONDS);
                    if (gotLockB) {
                        System.out.println("Thread1 has acquired LockB");
                    } else {
                        System.out.println("Thread1 failed to acquire LockB, releasing LockA");
                    }
                } else {
                    System.out.println("Thread1 failed to acquire LockA");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (gotLockB) {
                    LockB.unlock();
                }
                if (gotLockA) {
                    LockA.unlock();
                }
            }
        });

        Thread Thread2 = new Thread(() -> {
            boolean gotLockA = false;
            boolean gotLockB = false;
            try {
                gotLockA = LockA.tryLock(1, TimeUnit.SECONDS);
                if (gotLockA) {
                    System.out.println("Thread2 has acquired LockA");
                    gotLockB = LockB.tryLock(1, TimeUnit.SECONDS);
                    if (gotLockB) {
                        System.out.println("Thread2 has acquired LockB");
                    } else {
                        System.out.println("Thread2 failed to acquire LockB, releasing LockA");
                    }
                } else {
                    System.out.println("Thread2 failed to acquire LockA");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (gotLockB) {
                    LockB.unlock();
                }
                if (gotLockA) {
                    LockA.unlock();
                }
            }
        });

        Thread1.start();
        Thread2.start();
    }
}

通过以上锁获取顺序和超时机制,可以有效避免由于非重入锁导致的死锁情况。