gs) {
//创建两把锁
Object loker1=new Object();
Object loker2=new Object();
//创建两个不同的线程
Thread t1=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"试图获取到锁1");
synchronized(loker1){
System.out.println(Thread.currentThread().getName()+"获取到锁1");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"试图获取到锁2");
synchronized (loker2){
System.out.println(Thread.currentThread().getName()+"获取到锁2");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t1");
Thread t2=new Thread(()->{
HashMap<Integer, Integer> integerIntegerHashMap = new HashMap<>();
System.out.println(Thread.currentThread().getName()+"试图获取到锁2");
synchronized(loker2){
System.out.println(Thread.currentThread().getName()+"获取到锁2");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"试图获取到锁1");
synchronized (loker1){
System.out.println(Thread.currentThread().getName()+"获取到锁1");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t2");
t1.start();
t2.start();
}
}
其结果如下:
两个线程两把锁是多线程多把锁的特殊案例,我们下面将对多个线程多把锁的场景进行介绍:哲学家吃面问题:
五个哲学家吃一碗面,每个人左右两个 各有一双筷子,吃面规则是:每个哲学家先拿自己左手边的筷子,再拿自己右手边的筷子,吃一口之后把筷子放下,让其他哲学家吃面,对于一般情况而言,每个哲学家吃完一口,就把筷子放下了,不影响其他哲学家吃面,但是出现了下面这种情况:①号-⑤号哲学家依次拿起了自己左手边的筷子,然后想去拿自己右手的筷子,却发现右手边的筷子被右手边的科学家拿着不放,从而造成死锁问题。
我们去分析问题产生的原因:
①互斥访问:线程1拿到了锁A,线程2就不能再获取该锁
②不可抢占:已经获取锁资源的线程,除非自己手动释放锁,其他线程不能从这个线程的手中夺取锁资源
③保持与请求:线程1已经获取了锁A,这时候还想要再获取锁B
④循环等待:线程1等待线程2释放锁,线程2等待线程3释放锁,线程3等待线程1释放锁......
那么我们应该如何解决死锁问题呢?
我们依然从这四个方面进行分析
①互斥访问:锁的基本特性,不能打破
②不可抢占:锁的基本特性,不能打破
③保持与请求:从代码实现和设计的角度我们可以改变保持与请求的顺序,也就是获取锁的顺序来改变死锁问题
我们改变一下上述代码获取锁的顺序:
import java.util.HashMap;
/**
* @author tongchen
* @create 2023-02-09 17:34
*/
public class DeadLocker {
public static void main(String[] args) {
//创建两把锁
Object loker1=new Object();
Object loker2=new Object();
//创建两个不同的线程
Thread t1=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"试图获取到锁1");
synchronized(loker1){
System.out.println(Thread.currentThread().getName()+"获取到锁1");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"试图获取到锁2");
synchronized (loker2){
System.out.println(Thread.currentThread().getName()+"获取到锁2");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t1");
Thread t2=new Thread(()->{
HashMap<Integer, Integer> integerIntegerHashMap = new HashMap<>();
System.out.println(Thread.currentThread().getName()+"试图获取到锁1");
synchronized(loker1){
System.out.println(Thread.currentThread().getName()+"获取到锁1");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"试图获取到锁2");
synchronized (loker2){
System.out.println(Thread.currentThread().getName()+"获取到锁2");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t2");
t1.start();
t2.start();
}
}
我们通过改变获取锁的顺序解决了死锁问题
④循环等待:最常见的解决死锁的策略:我们设计破解死锁的一套获取锁的策略
我们针对哲学家吃面问题,来给出恰当的解决策略:
我们给每个筷子编上编号,每个哲学家先拿自己小号的筷子,然后再拿大号的筷子,拿不到则进行等待。
一到四号哲学家分别拿起了一到四号筷子,但是五号哲学家相对较小的筷子是一号,这时候先不拿筷子,这时四号拿起了五号筷子,吃完了面,放下了一双筷子,然后三二一号哲学家一次拿筷子吃面放下筷子,这时候五号哲学家就可以吃面了,死锁问题也就解决了。
- 银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。