《深入解析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---死锁问题

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

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

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

相关推荐
李慕婉学姐13 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆14 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin15 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200515 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉15 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国15 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824815 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈16 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9916 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹16 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理