第2课java_死锁
热度🔥:32 免费课程
授课语音
Java的死锁
1. 介绍
Java的死锁是指在多线程环境中,多个线程因争抢资源而导致相互等待,形成一个闭环,最终导致所有线程都无法继续执行,从而使程序无法响应。在实际开发中,死锁是一种常见的并发问题,需要谨慎对待。
必要条件
- 互斥条件:至少有一个资源是非共享的,一次只能被一个线程占用。
- 请求与保持条件:一个线程已经持有至少一个资源,并且在等待获取额外的资源,但这些额外的资源被其他线程持有。
- 不剥夺条件:资源不能被强制剥夺,必须由持有资源的线程主动释放。
- 循环等待条件:存在一个循环等待链,每个线程都在等待下一个线程持有的资源。
图示
预防方法
- 破坏请求与保持条件:在请求资源时,不持有其他资源;或者一次性请求所有需要的资源,避免后续请求。
- 破坏不剥夺条件:在获取资源的请求失败时,释放已持有的资源。
- 破坏循环等待条件:定义资源获取的顺序,确保所有线程按照相同的顺序获取资源。
避免方法
- 银行家算法:在分配资源前,系统评估是否会导致死锁,如果不会则分配资源,否则进程等待。
- 动态检查:在每个资源请求时进行动态检查,确保不会导致死锁。
2. 代码案例
死锁案例
package com.zhilitech.lock;
public class DeadlockDemo {
public static void main(String[] args) {
// 创建两个锁对象
final Object lock1 = new Object();
final Object lock2 = new Object();
// 创建两个线程,它们将尝试获取不同的锁,导致死锁
Thread thread1 = new Thread(new Task(lock1, lock2), "Thread-1");
Thread thread2 = new Thread(new Task(lock2, lock1), "Thread-2");
// 启动线程
thread1.start();
thread2.start();
}
// 任务类,尝试获取两个锁以模拟死锁
static class Task implements Runnable {
private final Object lock1; // 第一个锁
private final Object lock2; // 第二个锁
public Task(Object lock1, Object lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
// 第一个锁
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " 获取了锁1");
// 模拟一些操作
Thread.sleep(100);
// 第二个锁
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " 获取了锁2");
}
}
} catch (InterruptedException e) {
e.printStackTrace(); // 捕获并打印中断异常
}
}
}
}
方案一,避免死锁嵌套
package com.zhilitech.lock;
public class DeadlockSolutionDemo {
public static void main(String[] args) {
// 创建两个锁对象
final Object lock1 = new Object();
final Object lock2 = new Object();
// 创建两个线程,它们将以相同的顺序获取锁,避免死锁
Thread thread1 = new Thread(new Task(lock1, lock2), "Thread-1");
Thread thread2 = new Thread(new Task(lock1, lock2), "Thread-2");
// 启动线程
thread1.start();
thread2.start();
}
// 任务类,尝试以相同的顺序获取两个锁以避免死锁
static class Task implements Runnable {
private final Object lock1; // 第一个锁
private final Object lock2; // 第二个锁
public Task(Object lock1, Object lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
// 确保所有线程以相同的顺序获取锁
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " 获取了锁1");
// 模拟一些操作
Thread.sleep(100);
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " 获取了锁2");
}
}
} catch (InterruptedException e) {
e.printStackTrace(); // 捕获并打印中断异常
}
}
}
}
方案二,使用锁超时
package com.zhilitech.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class DeadlockSolutionWithTimeoutDemo {
public static void main(String[] args) {
// 创建两个可重入锁对象
final Lock lock1 = new ReentrantLock();
final Lock lock2 = new ReentrantLock();
// 创建两个线程,它们将尝试获取锁并使用超时机制来避免死锁
Thread thread1 = new Thread(new Task(lock1, lock2), "Thread-1");
Thread thread2 = new Thread(new Task(lock2, lock1), "Thread-2");
// 启动线程
thread1.start();
thread2.start();
}
// 任务类,尝试获取两个锁并使用超时机制来避免死锁
static class Task implements Runnable {
private final Lock lock1; // 第一个可重入锁
private final Lock lock2; // 第二个可重入锁
public Task(Lock lock1, Lock lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
// 尝试以锁1和锁2的顺序获取锁,并设置超时
boolean lock1Acquired = lock1.tryLock(1000, TimeUnit.MILLISECONDS);
if (lock1Acquired) {
try {
System.out.println(Thread.currentThread().getName() + " 获取了锁1");
// 模拟一些操作
Thread.sleep(100);
boolean lock2Acquired = lock2.tryLock(1000, TimeUnit.MILLISECONDS);
if (lock2Acquired) {
try {
System.out.println(Thread.currentThread().getName() + " 获取了锁2");
} finally {
lock2.unlock(); // 释放锁2
}
} else {
System.out.println(Thread.currentThread().getName() + " 未能获取锁2,放弃");
}
} finally {
lock1.unlock(); // 释放锁1
}
} else {
System.out.println(Thread.currentThread().getName() + " 未能获取锁1,放弃");
}
} catch (InterruptedException e) {
e.printStackTrace(); // 捕获并打印中断异常
}
}
}
}
通过这些案例和解决方案,希望大家对Java的死锁问题有更深入的理解,并掌握一些有效的预防和解决方法。