【JavaEE】死锁和避免方法

JavaEE 多线程场景中,锁是保障并发安全的核心手段。本文聚焦锁的设计与应用,剖析不同锁机制特性,助力解决资源竞争问题,提升分布式系统并发处理能力。

1.死锁

死锁是什么呢,可以理解为,家里面的锁卡死了,而且不能通过暴力的方法来拆开这把锁。程序卡在这里了。


  1. 一个线程,一把锁,连续上锁两次。

但是这种情况再Java中不存在,synchronized的特性可重入性,就排除了这个问题。

  1. 两个线程,两把锁,每个线程获取到一把锁之后,尝试获取对方的锁。
Java 复制代码
public static void main(String[] args) throws InterruptedException {
    final Object locker1 = new Object();
    final Object locker2 = new Object();

    Thread t1 = new Thread(()->{
        synchronized (locker1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (locker2){
                System.out.println("t1(t2)");
            }
            System.out.println("t1 locker1");
        }
    });

    Thread t2 = new Thread(()->{
        synchronized (locker2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (locker1){
                System.out.println("t1(t2)");
            }
            System.out.println("t1 locker1");
        }
    });
    t1.start();
    t2.start();
}

可以在JVM的jconsole.exe里面查看Java的线程,如图所示,两个线程都想要获取对方的锁,所以都被阻塞卡住了,这个位置为啥要加一个sleep呢,因为我们知道cpu的线程调用是随机调用执行的,休眠1s保证t1和t2都拿到了自己的锁,并且开始尝试获取对方的锁。

  1. N个线程,M把锁,产生死锁。这个就不得不提一个叫做哲学家就餐问题了:

2.如何避免死锁

构成死锁的四个必要条件:

  1. 锁是互斥的,当一个线程拿到锁之后,另外一个线程要获取锁的时候,就需要阻塞等待了。
  2. 锁是不可抢占的。

这两个条件是锁的使命,基本特性,没办法从这入手

  1. 请求和保持,线程拿着锁1的前提下,又请求锁2.
Java 复制代码
Thread t1 = new Thread(()->{
    synchronized (locker1){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("t1 结束");
    }
    synchronized (locker2){
        System.out.println("t1(t2)");
    }
});

Thread t2 = new Thread(()->{
    synchronized (locker2){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("t2 结束");
    }
    synchronized (locker1){
        System.out.println("t2(t1)");
    }
});

把两个线程的嵌套执行改成并行执行,这样就避免了死锁。

  1. 循环等待,多个线程,多把锁的等待情况,构成了**"循环"**。

eg:如两个线程,两把锁,A锁B,B锁A,形成循环了

eg:

这个我们可以约定好加锁的顺序,就可以破除循环等待了。

eg:我们约定好:每次获取都获取小的那边的锁,这样就可以避免循环等待了。

Java 复制代码
final Object locker1 = new Object();
final Object locker2 = new Object();

Thread t1 = new Thread(()->{
    synchronized (locker1){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        synchronized (locker2){
            System.out.println("t1(t2)");
        }
        System.out.println("t1 结束");
    }
});
Thread t2 = new Thread(()->{
    synchronized (locker1){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        synchronized (locker2){
            System.out.println("t2(t1)");
        }
        System.out.println("t2 结束");
    }
});

而哲学家就餐问题的话,1号哲学家拿1号筷子,2号哲学家拿2号筷子,3号哲学家拿3号筷子,4号哲学家拿4号筷子,5号哲学家也拿4号筷子 ,但是4号筷子被4号哲学家拿走了,于是5号哲学家就阻塞等待,而1号哲学家拿到了一双筷子,就可以就餐了,就不存在**"循环"**了。

3. 面试题:手搓死锁

这里就可以写两个线程,两把锁,每个线程获取到一把锁之后,尝试获取对方的锁的代码就可以产生死锁了。

相关推荐
咩咩啃树皮6 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
鱟鲥鳚7 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
大模型码小白8 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司9 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地10 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun31415910 小时前
TCP超时重传机制是为了解决什么问题?
java
莫逸风13 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
z1234567898613 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin52112314 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
做个文艺程序员14 小时前
Linux第24篇:Java应用监控体系搭建:Prometheus+Grafana可视化运维
java·grafana·prometheus