【JavaEE】——多重锁,死锁问题和解决思路
阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!
目录
一:加锁的“可重入性”
1:问题引入
2:问题分析
3:可重入性
(1)解释
(2)应用举例
4:内层机制分析
二:死锁
1:情景一
(1)代码解析
2:情景二——N个线程M把锁(进阶)
(1)举例:哲学家吃面条问题
(2)极端情况
(3)产生死锁的四个必要条件
①互斥性
②不可抢占性
③保持性
④循环等待
三:解锁
1:解锁思路
2:指定加锁顺序
引入:通过上一篇文章的学习,我们针对线程安全问题,简单认识了synchronized关键字,给操作“打包”,避免了一些多线程会出现的bug,通过本章学习我们将更进一步的学习Synchronized
一:加锁的“可重入性”
1:问题引入
我们给Thread线程,加两道锁,提问:hello能顺利打印出来吗
public class ThreadDemon24 {
public static void main(String[] args) {
Object locker = new Object();
Thread t1 = new Thread(()->{
synchronized(locker){
synchronized(locker){
System.out.println("hello");
}
}
});
t1.start();
}
}
2:问题分析
问:2已经加锁了,1再尝试加锁,不会出现“阻塞”情况吗?觉得会阻塞的扣1,不会阻塞的扣2~~
恭喜扣2的同学。由于是同一个线程,代码走到1时第一次加锁,走到2时,被加锁的对象locker知道第二次加锁是同一个线程(友军),所以就放行了,不会“阻塞”。
3:可重入性
(1)解释
“可重入性”就是在加锁机制中,同一个对象在同一个线程下第一次加完锁之后,再次遇到该线程下的对该对象的加锁,此时第二次加锁操作就会直接对该对象放行。就不会出现“阻塞”和“死锁”的情况。
(2)应用举例
我们平时写的代码,由于复杂的层层调用,无意中写出了双重加锁(锁重复)的情况,此时加锁的“可重复性”机制就能很好的解决这个问题。
4:内层机制分析
对于“可重入锁”机制来说,内部有两个信息
①当前“锁”是被哪个线程所持有的
②加锁次数的计数器
最外层的 { 这种括号负责加锁
最外层的 } 这种括号负责解锁
注:锁状态是没法直接查看的,我们只能通过jconsole来查看线程的状态,比如:BLOCKED
二:死锁
1:情景一
package thread;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Hua YY
* Date: 2024-09-22
* Time: 10:32
*/
public class ThreadDemon25 {
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) {
throw new RuntimeException(e);
}
synchronized(B){
System.out.println("尝试获取B锁");
}
}
});
Thread t2 = new Thread(()->{
synchronized(B){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(A){
System.out.println("尝试获取A锁");
}
}
});
t1.start();
t2.start();
}
}
(1)代码解析
通过 上面的代码结果的分析,t1和t2线程堵塞了,即t1想要获取B这个对象加锁,但是B已经被t2给锁上了,t2想要获取A这个对象,但是A已经被t1给锁上了。
2:情景二——N个线程M把锁(进阶)
(1)举例:哲学家吃面条问题
(五个滑稽老铁相当于五个线程,五根筷子相当于五把锁)有五个滑稽老铁一起吃面条,每个滑稽间放了一根筷子,比如一号滑稽老铁想要吃面条,就必须同时拿起1、5两根筷子,滑稽老铁吃面条的时候,其它人不能硬抢筷子。
滑稽老铁除了吃面条,就是放下筷子思考人生(线程不工作),由于每位哲学家什么时候吃面条,什么时候思考人生是不确定的(线程的随机调度),所以大部分情况下,筷子是够用的。
(2)极端情况
所有人都想吃面条,同时拿起左手边的筷子,此时想拿右手边的筷子时就g了(没人吃到面,也没人释放筷子,这就成了一个死锁了)
(3)产生死锁的四个必要条件
①互斥性
获取锁的过程是互斥的,一把锁只能被一个线程获取,另一个线程想要获取同一把锁就必须阻塞等待
②不可抢占性
一个线程拿到了锁,除非这个线程主动解锁,否则不会被别的线程强行把锁给抢走
③保持性
一个线程想获取第二把锁,那么第一把锁依旧还是存在的,不会消失
④循环等待
三:解锁
1:解锁思路
只要破坏掉上述产生死锁的四个必要条件中的随便一个,我们就能解锁了
但是①②点都是锁的最基本特性,不好破坏,③得具体情况具体分析,④破坏这种循坏的代码结构是最容易的。
2:指定加锁顺序
针对五把锁进行编号,五个滑稽老铁进行编号,指定一定的规则:先获取编号小的锁,在获取编号大的锁,这样就避免循环等待,避免了死锁。
当然这里还有很多别的方法就不一一赘述了~~