线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
为了更好地理解线程死锁,我们可以来看一个实际的例子。这个例子模拟了两个线程互相等待对方持有的资源,从而进入死锁的情况。代码如下:
java
public class DeadLockDemo {
private static Object resource1 = new Object(); // 资源1
private static Object resource2 = new Object(); // 资源2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + " get resource1");
try {
Thread.sleep(1000); // 休眠1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
}
}
}, "线程1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
try {
Thread.sleep(1000); // 休眠1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + " waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + " get resource1");
}
}
}, "线程2").start();
}
}
输出结果
txt
Thread[线程1,5,main] get resource1
Thread[线程2,5,main] get resource2
Thread[线程1,5,main] waiting get resource2
Thread[线程2,5,main] waiting get resource1
分析
在上述代码中,线程1 先获取了 resource1 的监视器锁,然后休眠1秒钟。在这1秒钟的时间内,线程2 有机会获取 resource2 的监视器锁。接着,线程1 和 线程2 分别尝试获取对方持有的资源,从而形成了互相等待的局面,这就产生了死锁。
死锁的四个必要条件
上面的例子符合产生死锁的四个必要条件:
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
如何避免死锁
为了避免死锁,可以采取以下几种策略:
1. 避免嵌套锁
尽量避免在一个锁内请求另一个锁,从而杜绝循环等待的可能性。
java
public class NoNestedLockDemo {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + " get resource1");
// 资源1的操作
}
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
// 资源2的操作
}
}, "线程1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
// 资源2的操作
}
synchronized (resource1) {
System.out.println(Thread.currentThread() + " get resource1");
// 资源1的操作
}
}, "线程2").start();
}
}
2. 锁的顺序
确保所有线程按照相同的顺序请求锁,可以有效避免循环等待。
java
public class LockOrderDemo {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + " get resource1");
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
}
}
}, "线程1").start();
new Thread(() -> {
synchronized (resource1) { // 按照相同的顺序请求锁
System.out.println(Thread.currentThread() + " get resource1");
synchronized (resource2) {
System.out.println(Thread.currentThread() + " get resource2");
}
}
}, "线程2").start();
}
}
3. 超时机制
使用超时机制来请求锁,如果超时未获得锁,则放弃请求并处理相应逻辑。
java
public class TimeoutLockDemo {
private static final ReentrantLock lock1 = new ReentrantLock();
private static final ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
try {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread() + " get lock1");
Thread.sleep(500); // Simulate some work
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread() + " get lock2");
} finally {
lock2.unlock();
}
} else {
System.out.println(Thread.currentThread() + " failed to get lock2");
}
} else {
System.out.println(Thread.currentThread() + " failed to get lock1");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
}
}, "线程1").start();
new Thread(() -> {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread() + " get lock2");
Thread.sleep(500); // Simulate some work
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread() + " get lock1");
} finally {
lock1.unlock();
}
} else {
System.out.println(Thread.currentThread() + " failed to get lock1");
}
} else {
System.out.println(Thread.currentThread() + " failed to get lock2");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
}
}, "线程2").start();
}
}