各位看官,大家早安午安晚安呀~~~
如果您觉得这篇文章对您有帮助的话
欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦
今天我们来学习多线程的synchronized---死锁问题
上一节我们初步了解了锁这个概念,通过synchronized修饰代码块把一个不是原子的操作变成原子的,那么synchronized只能修饰代码块吗?当然不是synchronized还可以修饰方法(实例方法和静态方法都可以的)
目录
首先补充一个知识(因为小编一直很迷)

1.synchronized修饰方法
java
synchronized public void increase(){ // synchronized修饰实例方法
count++;
}
public void increase1(){
synchronized (this){ // 用this作为锁对象
count++;
}
}
synchronized public static void increase2(){
count++;
}
public void increase3(){
synchronized (Demo1.class){ // 通过反射拿到这个类对象
count++;
}
}
一个.java文件编译 => .class(字节码文件) => 运行时.class文件被加载到JVM里面(就是说:JVM加载到内存中的数据结构就是类对象)
解释:
当JVM加载一个.class文件时,它会在内存中创建一个对应的数据结构,这个数据结构通常被称为"类对象"或"类结构"。这个类对象包含了类中定义的所有信息
类对象包括:类的属性,名字,类型,权限,类方法,继承哪个类,实现了哪个接口............
2:synchronized是一个可重入锁
那可重入锁是什么意思呢?
一个线程针对一个对象连续加锁两次不会出现死锁(我们等会会细说死锁)。满足这个要求的就是可重入锁。
我们拿一个代码进行举例:
java
synchronized (locker){
synchronized (locker){
count++;
}
}
按理说这个代码就卡住了:
解释: 我先给第一次给locker加锁,按理说下面第二次这个加锁操作肯定阻塞等待第一次加锁释放锁,我这个synchrinized才能给locker加锁。但是第二次不加锁第一次的加的锁就没办法解锁。这完全就死了(这种情况就死锁了)
但是!!!synchronized是可重入锁:就是可以连续给一个对象进行两次加锁
对象头里会有一个计数器(这个线程给这个对象加锁一个,计数器就+1)
解释对象头和计数器,线程给一个对象肯定会保存这个线程的信息
对象在加锁时会保存这个线程的信息,这些信息保存在对象头(隐藏里的隐藏信息)的Mark Word中。**每个对象都有一个对象头(Object Header),它包含了对象的一些元数据(譬如锁状态)。并且一个线程连续给一个对象加锁还会有一个计数器,**这个计数器是也存在于对象头里面的Mark Word中。这一机制确保了同一个线程可以多次获得同一个对象的锁。
解锁时:
并且一个对象被加三把锁时加入计数器的值 = 3 ,被解锁的时候也不是真的解锁而是解一把锁计数器-1,直到减到0才是真正的解锁
3:死锁
3.1.死锁的引入
刚才我们讲述了synchronized是一个可重入锁,我们给一个对象连续加锁并不会导致死锁(线程卡死了)。那什么情况下会出现死锁呢?
接下来给大家看一个代码
java
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(() ->{
synchronized (locker1){
try {
Thread.sleep(1000);
//这个休眠1s真的很重要,开启t1线程立马就拿到了 locker1这把锁,如果不休眠的话
//很容易一下子这个线程就把两个锁都拿到了
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 嵌套的锁
synchronized (locker2){
System.out.println("t1加锁成功");
}
}
});
Thread t2 = new Thread(() -> {
synchronized(locker2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 嵌套的锁
synchronized(locker1){
System.out.println("t2 加锁成功");
}
}
});
t1.start();
t2.start();
}
结果:什么都没有打印

我们进入jconsole看一下两个线程的状态

画个图解释一下

所以说:
所以两个线程都没有成功获得第二把锁
这个属于嵌套关系,线程A拿到locker1这把锁,又想要locker2这把锁(就可能出现死锁)
但是如果是并列关系(线程A先释放前面的锁,再获取下一把锁)(就不会死锁)
嵌套如何变成并列?改变代码结构!

这样就解决了,但是有时候代码结构不是那么好改变的。
刚才说到死锁形成环。。。就会出现一个经典的问题------哲学家就餐问题
3.2.哲学家就餐问题
先搞一张图表示这个问题

这就是导致了死锁问题(怎么解决呢)
说到怎么解决,就要知道形成死锁的几个必要条件,我们破坏其中一个就OK了
有这四个条件(其中前两个条件是synchronized的属性(我们改变不了))
1.互斥使用(锁的基本特性):一个线程拥有了锁A,另一个线程想要获取就只能阻塞等待
2.不可抢占(锁的基本特性):和条件一差不多,另一个线程只能等那个线程释放锁A,不能抢占过来
3.保持请求(代码结构):一个线程想要获得多把锁(嵌套,想要锁B但是又不想释放自己的锁B)(其实并形成环,你想要几个锁都没问题)
4.循环等待(代码结构):(条件三导致的条件四),等待的关系形成环了
条件三:其实有时候的需求就是需要进行获取多把锁,这个结构不好改变
条件四:我们约定好了加锁的顺序,就可以避免循环等待(针对锁进行编号,先加小锁再加大锁)
所以说哲学家就餐问题就可以这么解决!!!
我们可以规定每个哲学家只能先拿起数字小的筷子,哲学家B先拿起筷子,然后最后哲学家A面前只剩下了一个筷子5,但是他现在不能拿了(这个数字比较大)所以哲学家E就能够吃面条了,然后就通了。

上述就是synchronized---死锁问题
的全部内容了,死锁的出现,会让我们的程序陷入一个死循环的问题,但是我们只要知道死锁的成因,至少就知道了如何解决这个问题啦~~~预知后事如何,请听下回分解~~~
能看到这里相信您一定对小编的文章有了一定的认可。
有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~
您的支持就是我最大的动力!!!