目录
1.什么是死锁?
2.形成死锁的四个必要条件
3.死锁的三种情况
第一种情况:
举例:
举例:
第二种情况:两个线程 两把锁
举例:
第三种情况:N个线程 M把锁
哲学家进餐问题
1.什么是死锁?
死锁是指在并发系统中,两个或多个进程(或线程)互相等待对方所占有的资源而无法继续执行的情况。这种情况下,每个进程都在等待其他进程释放资源,导致所有进程都无法向前推进。
2.形成死锁的四个必要条件
1.互斥使用,获取锁的过程是互斥的。一个线程拿到了一把锁,另一个线程也想获取这把锁,就需要阻塞等待。
2.不可抢占。一个线程拿到了锁之后,只能主动解锁,不能让别的线程强行把锁抢走。
3.请求保持。一个线程拿到了锁A之后,在持有A的前提下,尝试获取B。
4.循环等待:存在一个进程链,每个进程都在等待下一个进程所占有的资源。
3.死锁的三种情况
第一种情况:
如果锁是不可进重入锁,并且一个线程对这把锁加锁了两次,那么它就会出现死锁的情况。
如果不是不可进重入锁。会出现下面的情况
举例:
package 多线程;
//死锁
public class ThreadDemo15 {
public static void main(String[] args) {
Object locker =new Object();
Thread t = new Thread(()-> {
synchronized (locker) {
synchronized (locker) {//当前由于事同一个线程,此时锁对象,就知道第二次加锁的线程,就是持有锁的线程。第二次操作会直接放行。
System.out.println("hello");
}
}//在这里解锁
});
t.start();
}
}
它就会打印一个hello。
如果是不可重进入锁
举例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockExample {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
lock.lock();
System.out.println("Thread is holding the lock");
// 尝试再次获取锁,会导致死锁
lock.lock();
System.out.println("This line will not be reached");
lock.unlock();
});
thread.start();
}
}
他就不会输出东西。
第二种情况:两个线程 两把锁
线程1 获取到 锁A
线程2 获取到 锁B
接下来,1 尝试获取B,2尝试获取A ,就会出现死锁。
一旦出现死锁,线程就会被卡住无法继续工作。
举例:
package 多线程;
//死锁
public class ThreadDemo16 {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()->{
synchronized (A){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//A尝试获取B,并没有释放A
synchronized (B){
System.out.println("t1 拿到了B");
}
}
});
Thread t2 = new Thread(()->{
synchronized (B){//约定加锁顺序,
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A){
System.out.println("t2 拿到了A");
}
}
});
t1.start();
t2.start();
}
}
当我们输出结果就会发现它一直没有输出任何东西。 当t1
线程持有A的锁资源时,它尝试获取B,而同时t2
线程持有B
的锁资源,它尝试获取A
,这样两个线程相互等待对方的锁资源,导致死锁的情况发生。
如何去解决这个问题呢,关键就在于死锁形成的四个必要条件,只要我们可以打破这四个必要条件,就不会形成死锁。这个题,我们约定好加锁的顺序的话,就不会出现死锁。
package 多线程;
//死锁
public class ThreadDemo16 {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()->{
synchronized (A){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//A尝试获取B,并没有释放A
synchronized (B){
System.out.println("t1 拿到了B");
}
}
});
Thread t2 = new Thread(()->{
synchronized (A){//约定加锁顺序,
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println("t2 拿到了A");
}
}
});
t1.start();
t2.start();
}
}
当t1
线程获取到A对象的锁资源后,它会尝试获取B对象的锁资源,但是此时B对象已经被t2
线程锁住了,因此t1
线程会进入等待状态。当t2
线程获取到B对象的锁资源后,它会尝试获取A对象的锁资源,此时A对象没有被锁住,因此t2
线程可以获取到A对象的锁资源,执行完成后释放锁资源,然后t1
线程才能继续执行,获取B对象的锁资源,避免了死锁的发生。
第三种情况:N个线程 M把锁
哲学家进餐问题
描述了五位哲学家围坐在一张圆桌旁,每个人面前都有一碗米饭和一只筷子。这些哲学家只能用左手和右手各拿一只筷子进食。问题是,如何安排他们的动作,使得每个哲学家都能进餐?
问题分析 :
由问题描述我们可以知道,一共有五个哲学家,也就是五个进程;五只筷子,也就是五个临界资源;因为哲学家想要进餐,必须要同时获得左边和右边的筷子,这就是要同时进入两个临界区(使用临界资源),才可以进餐。
问题解决:
- 一次只允许两个哲学家进餐,并且要求他们都拿到右手边的筷子后才能开始进食。
- 引入一个仲裁者,即一个额外的实体负责协调哲学家的动作,以避免死锁的发生。
- 使用资源分配算法,例如Dijkstra的银行家算法,来确保每个哲学家都能有足够的资源进餐。
希望大家多多支持!