Java多线程之死锁详解
什么是死锁
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局,若无外力作用,它们无法继续进行下去。
死锁的产生原因
死锁的产生通常由以下四个必要条件引起:
- 互斥条件: 资源不能被共享,只能被一个线程占用。
- 请求与保持条件: 线程已经保持了至少一个资源,并且当前正在请求另一个资源。
- 不剥夺条件: 资源不能强制性地被其他线程抢占,而只能由持有它的线程自行释放。
- 环路等待条件: 存在一种循环等待资源的情况,形成一个等待环路。
以上四种情况缺一不可,才会导致死锁。
如何避免死锁
避免死锁的方法一般有以下几种:
- 避免使用多个锁: 如果只使用一个锁,就不会有两个线程互相等待对方释放锁的情况。
- 按固定的顺序加锁: 保证所有线程按照同样的锁顺序获得锁,避免出现循环等待的情况。
- 超时机制: 在申请资源时,限定等待的时间,如果超过了这个时间还没有得到资源,就释放资源,这样就可以避免死锁。
示例一
下面是一个很简单的死锁示例:
public class DeadLockDemo {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1获得了锁1");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("线程1获得了锁2");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2获得了锁2");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("线程2获得了锁1");
}
}
}).start();
}
}
以上代码中,两个线程分别持有锁1和锁2。当线程1占用锁1并尝试占用锁2的同时,线程2同时占用了锁2,因此它们互相等待对方释放锁而无法继续执行,发生了死锁。
示例二
还有一种更为经典的死锁示例叫做“哲学家就餐问题”:
public class DiningPhilosophers {
private static final int NUMBER_OF_PHILOSOPHERS = 5;
private static final long SIMULATION_TIME_MS = 5000;
public static void main(String[] args) {
Fork[] forks = new Fork[NUMBER_OF_PHILOSOPHERS];
for (int i = 0; i < NUMBER_OF_PHILOSOPHERS; i++) {
forks[i] = new Fork();
}
Philosopher[] philosophers = new Philosopher[NUMBER_OF_PHILOSOPHERS];
for (int i = 0; i < NUMBER_OF_PHILOSOPHERS; i++) {
philosophers[i] = new Philosopher(forks[i], forks[(i + 1) % NUMBER_OF_PHILOSOPHERS]);
}
for (Philosopher philosopher : philosophers) {
new Thread(philosopher::start).start();
}
try {
Thread.sleep(SIMULATION_TIME_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Philosopher philosopher : philosophers) {
philosopher.setFull(true);
}
}
}
class Philosopher implements Runnable {
private Fork leftFork;
private Fork rightFork;
private boolean isFull = false;
public Philosopher(Fork leftFork, Fork rightFork) {
this.leftFork = leftFork;
this.rightFork = rightFork;
}
@Override
public void run() {
while (!isFull) {
synchronized (leftFork) {
System.out.println(Thread.currentThread().getName() + "拿起了左边的叉子");
synchronized (rightFork) {
System.out.println(Thread.currentThread().getName() + "拿起了右边的叉子");
eat();
}
}
}
}
private void eat() {
System.out.println(Thread.currentThread().getName() + "开始吃饭");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "吃饭结束,放下叉子");
}
public void setFull(boolean full) {
isFull = full;
}
}
class Fork {
}
以上代码中,5位哲学家围坐在一张圆桌旁,每个人的左右两侧都放着一把叉子。每个人要吃饭时,必须先拿起自己左右两侧的叉子,才能开始吃饭。
当所有人同时拿起自己左侧的叉子,但右边的叉子都被拿走了,这时所有人都无法继续吃饭,因为没有人释放它手里的叉子,从而陷入了死锁状态。
总结
死锁是多线程编程中的高难点,需要我们在程序设计中多方考虑,才能避免这种情况的出现。在实际开发中,可以通过调试工具和线程转储进行排查和分析,找到引起死锁的具体原因。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java多线程之死锁详解 - Python技术站