欢迎浏览高耳机的博客
希望我们彼此都有更好的收获
感谢三连支持!
你有没有想过,计算机程序也会"堵车"?
当老王忘了带门禁卡时:
门卫:请刷门禁卡
老王:我需要回家才能拿到门禁卡
门卫:你需要刷门禁卡才能回家
老王:可是我需要回家才能拿到门禁卡
.........
在多线程编程中,死锁是一个常见的问题,它会导致程序的执行效率降低,甚至导致程序完全停止响应。死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局。当线程A持有资源B,等待资源C,而线程B持有资源C,等待资源B时,就会发生死锁。
死锁的四个必要条件
- 互斥条件:资源不能被多个线程同时使用。
- 占有和等待条件:线程至少占有一个资源,并且等待获取其他线程占有的资源。
- 不可抢占条件:资源只能由占有它的线程自愿释放。
- 循环等待条件:存在一个线程的集合,其中每个线程都在等待下一个线程所占有的资源。
代码实例
下面是一个简单的Java代码示例,演示了如何通过synchronized
关键字来创建死锁:
public class Demo10 {
private static Object locker1 = new Object();
private static Object locker2 = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (locker1) {
System.out.println("t1 加锁 locker1 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// !!!!!!!!!!!!!!注意,下面的synchronized还在第一个synchronized括号里面
// 这样才能保证没有释放第一把锁!!!!!!!!!!!!!!!!!!!!!!!!!!
//thread2中同理
synchronized (locker2){
System.out.println("t1 加锁 locker2 完成");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (locker2) {
System.out.println("t2 加锁 locker2 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker1){
System.out.println("t2 加锁 locker1 完成");
}
}
});
thread1.start();
thread2.start();
}
}
在这个例子中,线程1首先获取locker1
的锁,同时线程2则首先获取locker2
的锁,线程12同时进入睡眠等待,以确保它们都获得了自己对应的锁,接着它们尝试获取对方已经持有
的锁。由于两个线程都在等待对方释放锁,因此它们都将永远等待下去,形成死锁。
解决死锁的策略
虽然死锁是一个复杂的问题,但是我们可以采取一些策略来避免或解决它。以下是一些常见的解决死锁的策略:
-
避免占有和等待条件:要求线程在执行前一次性获取所有需要的资源,这样就不会有线程在持有资源的同时等待其他资源。
-
资源分配图:使用资源分配图来表示系统中资源的分配情况,通过检测是否存在循环等待来确定是否发生死锁。
-
死锁检测算法:系统周期性地检测是否存在死锁,一旦发现死锁,采取措施解除。
-
破坏不可抢占条件:允许线程抢占其他线程占有的资源,或者在等待一定时间后自动释放资源。
-
使用定时锁:在获取锁时使用超时机制,如果超过指定时间无法获取锁,则放弃并重试。
-
顺序加锁:规定所有线程按照相同的顺序获取锁,这样可以避免循环等待条件的发生。
-
死锁预防:通过设计来确保死锁的四个必要条件不会同时满足。
-
使用并发库:使用Java并发库中的类,如
ReentrantLock
和Semaphore
,它们提供了更灵活的锁机制,有助于避免死锁。
下面是一个使用ReentrantLock
和tryLock
方法来避免死锁的代码示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo10 {
private static final Lock locker1 = new ReentrantLock();
private static final Lock locker2 = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
if (locker1.tryLock()) {
System.out.println("t1 加锁 locker1 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (locker2.tryLock()) {
System.out.println("t1 加锁 locker2 完成");
locker2.unlock();
}
}
} finally {
if (locker1.isHeldByCurrentThread()) {
locker1.unlock();
}
}
});
Thread thread2 = new Thread(() -> {
try {
if (locker2.tryLock()) {
System.out.println("t2 加锁 locker2 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (locker1.tryLock()) {
System.out.println("t2 加锁 locker1 完成");
locker1.unlock();
}
}
} finally {
if (locker2.isHeldByCurrentThread()) {
locker2.unlock();
}
}
});
thread1.start();
thread2.start();
}
}
在这个例子中,我们使用了ReentrantLock
和tryLock
方法来尝试获取锁,如果无法获取锁,则线程不会无限期地等待,而是会释放已持有的锁并重试。这样可以有效地避免死锁的发生。
总结
死锁是多线程编程中的一个复杂问题,但是通过理解其产生的原因和条件,我们可以采取有效的策略来避免或解决它。在实际编程中,合理地设计资源的分配和访问策略,是避免死锁的关键。同时,利用现代并发库提供的工具和类,可以帮助我们更好地管理和协调多线程之间的资源访问,从而减少死锁的发生。
希望这篇博客能为你理解多线程编程中的死锁提供一些帮助。
如有不足之处请多多指出。
我是高耳机。