欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 《深入解析Java synchronized死锁:从可重入锁到哲学家就餐问题》

《深入解析Java synchronized死锁:从可重入锁到哲学家就餐问题》

2025/3/12 11:54:36 来源:https://blog.csdn.net/2302_80639556/article/details/145079283  浏览:    关键词:《深入解析Java synchronized死锁:从可重入锁到哲学家就餐问题》

各位看官,大家早安午安晚安呀~~~

如果您觉得这篇文章对您有帮助的话

欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦

今天我们来学习多线程的synchronized—死锁问题

上一节我们初步了解了锁这个概念,通过synchronized修饰代码块把一个不是原子的操作变成原子的,那么synchronized只能修饰代码块吗?

当然不是synchronized还可以修饰方法(实例方法和静态方法都可以的)

目录

1.synchronized修饰方法

2:synchronized是一个可重入锁

3:死锁

3.1.死锁的引入

3.2.哲学家就餐问题


首先补充一个知识(因为小编一直很迷)

1.synchronized修饰方法

 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是一个可重入锁

那可重入锁是什么意思呢?

一个线程针对一个对象连续加锁两次不会出现死锁(我们等会会细说死锁)。满足这个要求的就是可重入锁。

我们拿一个代码进行举例:

       synchronized (locker){synchronized (locker){count++;}}

按理说这个代码就卡住了:

解释:  我先给第一次给locker加锁,按理说下面第二次这个加锁操作肯定阻塞等待第一次加锁释放锁,我这个synchrinized才能给locker加锁。但是第二次不加锁第一次的加的锁就没办法解锁。这完全就死了(这种情况就死锁了)

但是!!!synchronized是可重入锁:就是可以连续给一个对象进行两次加锁

对象头里会有一个计数器(这个线程给这个对象加锁一个,计数器就+1)

解释对象头和计数器,线程给一个对象肯定会保存这个线程的信息

对象在加锁时会保存这个线程的信息,这些信息保存在对象头(隐藏里的隐藏信息)的Mark Word中。每个对象都有一个对象头(Object Header),它包含了对象的一些元数据(譬如锁状态)。并且一个线程连续给一个对象加锁还会有一个计数器,这个计数器是也存在于对象头里面的Mark Word中。这一机制确保了同一个线程可以多次获得同一个对象的锁。

解锁时:

并且一个对象被加三把锁时加入计数器的值 = 3 ,被解锁的时候也不是真的解锁而是解一把锁计数器-1,直到减到0才是真正的解锁

3:死锁

3.1.死锁的引入

刚才我们讲述了synchronized是一个可重入锁,我们给一个对象连续加锁并不会导致死锁(线程卡死了)。那什么情况下会出现死锁呢?

接下来给大家看一个代码

    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—死锁问题

的全部内容了,死锁的出现,会让我们的程序陷入一个死循环的问题,但是我们只要知道死锁的成因,至少就知道了如何解决这个问题啦~~~预知后事如何,请听下回分解~~~

能看到这里相信您一定对小编的文章有了一定的认可。

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~

您的支持就是我最大的动力​​​!!!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词