面试题答案
一键面试非重入锁导致死锁的场景
- 场景描述:
- 假设有两个资源
ResourceA
和ResourceB
,并且有两把非重入锁LockA
和LockB
分别用于保护这两个资源。 - 有两个
Worker
线程Thread1
和Thread2
。Thread1
首先获取LockA
,然后尝试获取LockB
。而Thread2
首先获取LockB
,然后尝试获取LockA
。如果Thread1
获取LockA
后,Thread2
获取LockB
,此时Thread1
等待LockB
,Thread2
等待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();
}
}
- 原理分析:
- 非重入锁意味着如果一个线程已经持有了锁,再次尝试获取该锁时会被阻塞。在上述场景中,两个线程以不同顺序获取锁,且都不释放已持有的锁就尝试获取另一个锁,从而导致互相等待,形成死锁。
避免死锁的方法
- 锁的获取顺序:
- 方法描述:所有线程都按照相同的顺序获取锁。例如,在上述场景中,无论是
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();
}
}
- 超时机制:
- 方法描述:在获取锁时设置一个超时时间。如果在超时时间内没有获取到锁,则放弃获取并释放已获取的锁,从而避免无限等待。在Java中,可以使用
tryLock(long timeout, TimeUnit unit)
方法。 - 示例代码修改:
- 方法描述:在获取锁时设置一个超时时间。如果在超时时间内没有获取到锁,则放弃获取并释放已获取的锁,从而避免无限等待。在Java中,可以使用
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();
}
}
通过以上锁获取顺序和超时机制,可以有效避免由于非重入锁导致的死锁情况。