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

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

一、什么是死锁?    

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


二、“哲学家就餐问题”

问题描述:


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


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 的锁。

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

相关文章
|
4月前
|
安全 Java 程序员
【多线程-从零开始-肆】线程安全、加锁和死锁
【多线程-从零开始-肆】线程安全、加锁和死锁
79 0
|
6月前
|
Java
Java多线程-死锁的出现和解决
死锁是指多线程程序中,两个或以上的线程在运行时因争夺资源而造成的一种僵局。每个线程都在等待其中一个线程释放资源,但由于所有线程都被阻塞,故无法继续执行,导致程序停滞。例如,两个线程各持有一把钥匙(资源),却都需要对方的钥匙才能继续,结果双方都无法前进。这种情况常因不当使用`synchronized`关键字引起,该关键字用于同步线程对特定对象的访问,确保同一时刻只有一个线程可执行特定代码块。要避免死锁,需确保不同时满足互斥、不剥夺、请求保持及循环等待四个条件。
|
6月前
|
Java 测试技术 PHP
父子任务使用不当线程池死锁怎么解决?
在Java多线程编程中,线程池有助于提升性能与资源利用效率,但若父子任务共用同一池,则可能诱发死锁。本文通过一个具体案例剖析此问题:在一个固定大小为2的线程池中,父任务直接调用`outerTask`,而`outerTask`再次使用同一线程池异步调用`innerTask`。理论上,任务应迅速完成,但实际上却超时未完成。经由`jstack`输出的线程调用栈分析发现,线程陷入等待状态,形成“死锁”。原因是子任务需待父任务完成,而父任务则需等待子任务执行完毕以释放线程,从而相互阻塞。此问题在测试环境中不易显现,常在生产环境下高并发时爆发,重启或扩容仅能暂时缓解。
100 0
|
6月前
|
安全 算法 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
102 6
|
6月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
107 5
|
6月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
101 3
|
7月前
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。
125 0
|
8月前
|
SQL 安全 Java
JUC多线程-线程池-Thredalocal-CAS-AQS-死锁
JUC多线程-线程池-Thredalocal-CAS-AQS-死锁
|
8月前
|
Java
java线程之死锁
java线程之死锁
|
8月前
|
Java
synchronized关键字在Java中为多线程编程提供了一种简便的方式来管理并发,防止数据竞争和死锁等问题
Java的`synchronized`关键字确保多线程环境中的数据一致性,通过锁定代码段或方法防止并发冲突。它可修饰方法(整个方法为临界区)或代码块(指定对象锁)。例如,同步方法只允许一个线程执行,同步代码块则更灵活,可锁定特定对象。使用时需谨慎,以避免性能影响和死锁。
48 0