多线程(死锁)

简介: 多线程(死锁)

什么是死锁

先介绍独占性的资源 : 即再同一时刻某种资源只能由一个进程使用, 比如打印机, 同一时刻一台打印机不能有两个输出结果

那么两个进程独占性的访问某个资源, 从而等待另一个资源的执行结果, 会导致两个进程都阻塞, 并且两个进程都不会释放各自的资源, 这种情况就是 “死锁”

死锁的典型情况

  1. 在一个线程中, 对同一个对象连续加锁多次, 如果是不可重入锁, 就会产生死锁问题
  2. 两个线程两把锁, 线程 t1 和 t2 各自针对 对象 A 和 B 加锁, 再尝试获取对方已经加锁的对象
  3. 多个线程多把锁 (典型实例 : 哲学家就餐问题)

可重入不可重入

在一个线程中, 在已经给一个对象上锁(还未解锁的情况下), 再次尝试给该对象加锁

可重入锁(例如 synchronized) , 会在锁内记录是哪个线程拥有我, 如果该线程再次尝试给我加锁, 就允许 (实际上只加了一次锁, 后续的再次加锁只是允许你使用该对象, 不是再多加一次锁)


不可重入, 在一个线程内, 如果针对一个对象多次加锁, 由于第一次加锁过后没有解锁, 所以后续的加锁操作就会进入锁竞争, 前面加的锁不释放, 后面在等前面的锁释放, 就会造成死锁情况

死锁的四个必要条件

  1. 互斥使用 (锁的基本特征): 线程 t1 拿到了锁, 线程 t2 就只能等待
  1. 不可抢占: 线程 t1那到了锁, 必须是 t1 主动释放锁, 不能是因为别的线程需要使用的时候, 强行抢夺锁
  2. 请求和保持: 线程 t1 拿到 锁A 之后, 再尝试获取 锁B, 此时 t1 仍持有 锁A, 不会因为获取 锁B 就把 锁A 给释放了
  3. 循环等待: 线程 t1 持有锁A, 线程 t2 持有 锁B, 在此基础上 线程 t1尝试获取 锁B, 并且线程 t2 尝试获取 锁A, 二者都不释放自己拥有的锁, 又尝试获取彼此拥有的锁

如何破除死锁

破除死锁其实就是破除上述的四个基本特征 (任意一个条件打破, 死锁就被破除)

对于 synchronized

互斥使用 / 不可抢占 / 请求和保持 都是 synchronized 的基本特征

因此我们只能从 “循环等待” (也就是代码逻辑)下手


最常用的一种死锁阻止策略就是 “锁排序”. 假设有 N 个线程尝试获取 M 把锁, 可以针对 M 把锁进行编号 (1, 2, 3, … , M ), N 个线程尝试获取锁的时候, 都按照编号, 由小到大的顺序来获取锁, 就可以避免环路等待

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