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

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

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

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

今天我们来学习多线程的synchronized---死锁问题
上一节我们初步了解了锁这个概念,通过synchronized修饰代码块把一个不是原子的操作变成原子的,那么synchronized只能修饰代码块吗?

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

目录

1.synchronized修饰方法

2:synchronized是一个可重入锁

3:死锁

3.1.死锁的引入

3.2.哲学家就餐问题


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

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

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

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

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~
您的支持就是我最大的动力​​​!!!

相关推荐
V+zmm1013410 分钟前
电器维修系统小程序+论文源码调试讲解
java·数据库·微信小程序·小程序·毕业设计
PawSQL16 分钟前
推理模型对SQL理解能力的评测:DeepSeek r1、GPT-4o、Kimi k1.5和Claude 3.7 Sonnet
java·数据库·人工智能·sql·sql优化·pawsql·deepseek
BeanInJ38 分钟前
JAVA字符串与正则表达式
java·正则表达式
珹洺1 小时前
数据库系统概论(三)数据库系统的三级模式结构
java·运维·服务器·数据库·oracle
算法与编程之美1 小时前
冒泡排序
java·开发语言·数据结构·算法·排序算法
Aphelios3801 小时前
Java 学习记录:基础到进阶之路(一)
java·开发语言·学习·idea
程序员麻辣烫2 小时前
晋升系列4:学习方法
java·数据库·程序人生·学习方法
爱学习的小王!2 小时前
有关MyBatis的动态SQL
java·笔记·sql·学习·mybatis
斑鸠喳喳2 小时前
模块系统 JPMS
java·后端
苦逼的老王2 小时前
java之uniapp实现门店地图
java·开发语言·uni-app