线程 --- 死锁的一些原因总结

简介: 线程 --- 死锁的一些原因总结

一、什么是死锁?    

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,一直保持死锁状态。


二、“哲学家就餐问题”

问题描述:


  • 有五位沉默的哲学家围坐在一张圆桌旁,他们只会 思考 和 吃 ,思考的时候放下筷子,吃的话需要拿左右两边筷子。 如下图所示!
  • 如果他们随便拿 ! 都想吃东西了,都拿自己左手边的话,这样每个人都吃不到,这种局面就是 死锁,一直死锁 会造成 饿死!!
  • 那么现在有一个问题,我们需要想出一种方案,如何保证哲学家们可以交替吃饭和思考,而不会被饿死。


3fb802c3c4134e4593433e1bcef3a862.png



解决问题:

1. semaphore 设置信号量

  • 我们可以发现 五个人同时吃就会造成死锁!


  • 而四个人就不会,因为有一个人正在吃,这个人一旦吃完 其他阻塞等待的人就可以继续吃了。
  • 所有假设有n个哲学家,我们通过设置一个信号初始值 n-1

2. 制定规则

我们也可让其左右都有筷子的时候才可以吃,也就是要求哲学家同时拿起俩筷子。

三、如何避免死锁

1. 产生死锁的四个条件


  • 互斥条件:一个资源被一个线程占用,其他线程不能使用。(哲学家拿了一个筷子,别的哲学家不能拿)
  • 不可抢占:在末使用完之前,不能强行剥夺。(一个哲学家正在吃,别的哲学家不能抢筷子)


  • 请求与保持条件:请求别的资源的时候,自己的资源也不会自动释放。(哲学家请求拿右手筷子,但左手已经拿到的筷子仍拿着)  
  • 循环等待:存在一个等待队列(例子:每个哲学家都拿左手的筷子,等待右手的筷子)


2. 避免死锁的方式

  1. 互斥使用和不可抢占是 锁的特性,我们无法打破
  2. 请求和保持?大部分情况都无法改变!只有特定情况才能从这里入手
  3. 循环等待:最容易破坏


3. 通过 循环等待 解决死锁的方法

给线程加顺序

死锁的情况:

当t1线程获取到 lock1 想拿 lock2 ,t2 线程获取到 lock2 想拿 lock1。

这样就会产生循环等待,而造成死锁的情况。


    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (lock1){
                synchronized (lock2){
                    System.out.println(Thread.currentThread().getName());
                }
            }
        },"t1");
        Thread t2 = new Thread(()->{
           synchronized (lock2){
               synchronized (lock1){
                   System.out.println(Thread.currentThread().getName());
               }
           }
        },"t2");
        t1.start();
        t2.start();
    }

如何解决?

约定顺序加锁!比如下面的 t1 和 t2 线程都是先加 lock1 再加 lock2 的锁。

这样的话,就会避免循环等待。

相关文章
|
2月前
|
安全 算法 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
65 6
|
2月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
71 5
|
2月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
64 3
|
2月前
|
Java
Java多线程-死锁的出现和解决
死锁是指多线程程序中,两个或以上的线程在运行时因争夺资源而造成的一种僵局。每个线程都在等待其中一个线程释放资源,但由于所有线程都被阻塞,故无法继续执行,导致程序停滞。例如,两个线程各持有一把钥匙(资源),却都需要对方的钥匙才能继续,结果双方都无法前进。这种情况常因不当使用`synchronized`关键字引起,该关键字用于同步线程对特定对象的访问,确保同一时刻只有一个线程可执行特定代码块。要避免死锁,需确保不同时满足互斥、不剥夺、请求保持及循环等待四个条件。
|
2月前
|
Java 测试技术 PHP
父子任务使用不当线程池死锁怎么解决?
在Java多线程编程中,线程池有助于提升性能与资源利用效率,但若父子任务共用同一池,则可能诱发死锁。本文通过一个具体案例剖析此问题:在一个固定大小为2的线程池中,父任务直接调用`outerTask`,而`outerTask`再次使用同一线程池异步调用`innerTask`。理论上,任务应迅速完成,但实际上却超时未完成。经由`jstack`输出的线程调用栈分析发现,线程陷入等待状态,形成“死锁”。原因是子任务需待父任务完成,而父任务则需等待子任务执行完毕以释放线程,从而相互阻塞。此问题在测试环境中不易显现,常在生产环境下高并发时爆发,重启或扩容仅能暂时缓解。
|
3月前
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。
|
4月前
|
Arthas 监控 Java
深入解析与解决高并发下的线程池死锁问题
在高并发的互联网应用中,遇到线程池死锁问题导致响应延迟和超时。问题源于库存服务的悲观锁策略和线程池配置不当。通过以下方式解决:1) 采用乐观锁(如Spring Data JPA的@Version注解)替换悲观锁,减少线程等待;2) 动态调整线程池参数,如核心线程数、最大线程数和拒绝策略,以适应业务负载变化;3) 实施超时和重试机制,减少资源占用。这些改进提高了系统稳定性和用户体验。
140 2
|
4月前
|
Java
在Java中,死锁是指两个或多个线程互相等待对方释放资源,从而导致所有线程都无法继续执行的情况。
【6月更文挑战第24天】在Java并发中,死锁是多线程互相等待资源导致的僵局。避免死锁的关键策略包括:防止锁嵌套,设定固定的加锁顺序,使用`tryLock`带超时,避免无限等待,减少锁的持有时间,利用高级同步工具如`java.util.concurrent`,以及实施死锁检测和恢复机制。通过这些方法,可以提升程序的并发安全性。
38 1
|
4月前
|
Java
死锁是线程间争夺资源造成的无限等待现象,Java示例展示了两个线程各自持有资源并等待对方释放,导致死锁。`
【6月更文挑战第20天】死锁是线程间争夺资源造成的无限等待现象,Java示例展示了两个线程各自持有资源并等待对方释放,导致死锁。`volatile`保证变量的可见性和部分原子性,确保多线程环境中值的即时更新。与`synchronized`相比,`volatile`作用于单个变量,不保证原子操作,同步范围有限,但开销较小。`synchronized`提供更全面的内存语义,保证原子性和可见性,适用于复杂并发控制。
35 3
|
4月前
|
Python
Python多线程中递归锁如何解决死锁问题的详细阐述
Python多线程中递归锁如何解决死锁问题的详细阐述