多线程之死锁

简介: 多线程之死锁


1.什么是死锁?

谈到死锁,程序猿们都心存忌惮,因为程序一旦出现死锁,就会导致线程无法继续执行后面的工作了,那么这个程序就会出现非常大的bug。而且死锁一般来说,不易发现,在开发阶段如果写出了死锁代码,那么很可能测试是测试不出来的,所以出现死锁问题还是非常不爽的。下面将详细谈谈关于死锁。

2.可重入与不可重入

一个线程针对同一个对象连续加锁两次。如果没有出现问题就是可重入的,如果出现问题了那么就是不可重入的。

c0337d24995a4ba8b8939b5601e19ecd.png

此时锁对象是this,只要有线程调用add方法,那么进入add方法的时候就会先加锁,进去之后又遇到了代码块,将再次尝试加锁。站在this(锁对象)的角度,它认为自己已经被线程占用了,这里的第二次加锁是否要阻塞等待呢?如果允许第二次加锁,那么这个锁就是可重入的;如果不允许第二次加锁,第二次加锁会发生阻塞等待,那么就是不可重入的,这个时候就发生僵持了,就发生死锁了。在java中,把synchronized设定成可重入的了。  

3.发生死锁的三个典型情况

1.一个线程针一把锁连续加锁两次。如果锁是不可重入锁,就会死锁。

2.两个线程两把锁,t1先对锁A加锁,t2先对锁B加锁,然后各自再尝试获取对方的锁,就会死锁。代码示例:

public class ThreadDemo10 {
    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(()->{
            synchronized (A){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                    System.out.println("A去拿B的锁了");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (B){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){
                    System.out.println("B去拿A的锁了");
                }
            }
        });
    }
}

运行结果如下:(出现死锁了)

用jconsole查看线程情况:


这里很明显可以看到这两把锁互相僵持住了,出现了死锁问题。针对这样的死锁问题,也是需要借助像jconsole 这样的工具来进行定位的,看线程的状态和调用栈,就可以分析出代码是在哪里死锁了。

3.多个线程多把锁

因为由于操作系统的随机调度,多个线程对多把锁可能会随机加锁,那么就会出现第二点那样的情况,相互僵持,出现死锁。如何解决这个问题呢?可以把锁进行编号,然后对线程进行约定,默认从编号小的开始加锁,这样就可以死锁问题。然后我们将第二点的实例代码改造一下,默认都先对A加锁,再对B进行加锁,这样就不会出现死锁了。代码示例:

Thread t1 = new Thread(()->{
    synchronized (A){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B){
            System.out.println("t1把A和B都拿到了");
        }
    }
});
Thread t2 = new Thread(()->{
    synchronized (A){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B){
            System.out.println("t2把A和B都拿到了");
        }
    }
});

运行结果如下:

 

4.发生死锁的四个必要条件

1.互斥使用,线程1拿到了锁,线程2就得等着。(锁的基本特性)

2.不可抢占,线程1拿到锁之后,必须是线程1主动释放,不能说是线程2就把锁给强行获取到。

3.请求和保持,线程1拿到锁A之后,再尝试获取锁B,A这把锁还是保持的。(不会因为获取锁B就把A给释放了)

4.循环等待,线程1尝试获取到锁A和锁B,线程2尝试获取到锁B和锁A。线程1在获取B的时候等待线程2释放B;同时线程2在获取A的时候等待线程1释放A

5.如何破除死锁?

在上面也已经谈到了,破除死锁的办法就是给锁编号,然后指定一个固定的顺序(比如从小到大)来加锁。任意线程加多把锁的时候,都让线程遵守上述顺序,此时循环等待就自然破除了。


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

相关实验场景

更多