深入理解与避免Java 死锁

在 Java 编程中,死锁是一个让人头疼但又至关重要的问题。理解死锁的产生条件以及如何避免死锁,对于编写高效、稳定的多线程程序至关重要。本文将深入探讨 Java 死锁的四个必要条件,并通过具体的例子和解决方案帮助读者更好地理解和避免死锁。

一、引言

在多线程编程中,线程之间的协作和资源共享是常见的需求。然而,如果不加以小心处理,就可能会出现死锁的情况。死锁会导致程序无法继续执行,严重影响系统的性能和可靠性。因此,了解死锁的产生条件以及如何避免死锁是每个 Java 开发者都应该掌握的知识。

二、什么是死锁

死锁是指两个或多个线程相互等待对方释放资源,从而导致程序无法继续执行的情况。例如,线程 A 持有资源 X,等待资源 Y;而线程 B 持有资源 Y,等待资源 X。这样,两个线程就陷入了死锁状态,无法继续执行。

三、产生死锁的四个必要条件

(一)互斥条件

  1. 解释
    • 互斥条件是指一个资源每次只能被一个线程使用。这就好比一个房间只能被一个人占用,如果两个人同时想进入这个房间,就必须等待其中一个人先出来。
    • 在 Java 中,很多资源都是互斥的,比如文件、数据库连接、锁等。当一个线程获得了这些资源的锁时,其他线程就必须等待,直到这个线程释放锁。
  2. 例子
    • 假设我们有一个打印机资源,线程 A 正在使用打印机打印文件,这时线程 B 也想使用打印机,但是在 A 使用完之前,B 就无法使用,因为打印机这个资源是互斥的。
    • 又如,在 Java 中使用synchronized关键字来实现线程同步时,被synchronized修饰的方法或代码块就相当于一个互斥资源,同一时间只能被一个线程访问。

(二)请求与保持条件

  1. 解释
    • 请求与保持条件是指一个线程因请求资源而阻塞时,对已获得的资源保持不放。这就像一个人在图书馆里,已经借了几本书(已获得的资源),但又看到了另一本更好的书(新的资源),于是他去请求借阅那本书,但是在请求新资源的时候,他并不愿意放下已经借到的书。
    • 在 Java 中,一个线程可能已经获得了一些资源,然后又去请求新的资源。在等待新资源的过程中,它不会释放已经拥有的资源,这就可能导致死锁。
  2. 例子
    • 假设有两个资源 X 和 Y,线程 A 先获得了资源 X,然后又去请求资源 Y。在等待资源 Y 的过程中,线程 A 不会释放资源 X。与此同时,线程 B 先获得了资源 Y,然后又去请求资源 X。这样,两个线程就陷入了死锁状态,因为它们都在等待对方释放资源。
    • 以下是一个用 Java 代码演示请求与保持条件导致死锁的例子:
public class RequestAndHoldDeadlockExample {public static Object resourceX = new Object();public static Object resourceY = new Object();public static void main(String[] args) {Thread threadA = new Thread(() -> {synchronized (resourceX) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resourceY) {System.out.println("Thread A acquired both resources.");}}});Thread threadB = new Thread(() -> {synchronized (resourceY) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resourceX) {System.out.println("Thread B acquired both resources.");}}});threadA.start();threadB.start();}
}

在这个例子中,线程 A 先获得了资源 X,然后去请求资源 Y;线程 B 先获得了资源 Y,然后去请求资源 X。由于两个线程都在等待对方释放资源,所以就发生了死锁。

(三)不剥夺条件

  1. 解释
    • 不剥夺条件是指进程已经获得的资源,在未使用完之前,不能强行剥夺。这就像一个人已经拿到了一本书,在他看完这本书之前,别人不能强行把这本书从他手里夺走。
    • 在 Java 中,一个线程已经获得了某个资源的锁,其他线程不能强行剥夺这个锁,只能等待这个线程主动释放锁。
  2. 例子
    • 假设线程 A 获得了资源 X 的锁,正在使用资源 X。这时,线程 B 也想使用资源 X,但是它不能强行剥夺线程 A 对资源 X 的锁,只能等待线程 A 主动释放锁。
    • 以下是一个用 Java 代码演示不剥夺条件导致死锁的例子:
public class NonPreemptionDeadlockExample {public static Object resource = new Object();public static void main(String[] args) {Thread threadA = new Thread(() -> {synchronized (resource) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread A finished using resource.");}});Thread threadB = new Thread(() -> {synchronized (resource) {System.out.println("Thread B acquired resource.");}});threadA.start();threadB.start();}
}

在这个例子中,线程 A 获得了资源的锁,然后进入睡眠状态,模拟正在使用资源。线程 B 试图获得资源的锁,但是由于不剥夺条件,它只能等待线程 A 主动释放锁。如果线程 A 一直不释放锁,那么线程 B 就会一直等待,从而导致死锁。

(四)循环等待条件

  1. 解释
    • 循环等待条件是指若干线程之间形成一种头尾相接的循环等待资源关系。这就像几个人围成一圈,每个人都想要他右边的人的东西,同时又拿着自己左边的人想要的东西。这样就形成了一个循环等待的关系,谁也得不到自己想要的东西。
    • 在 Java 中,如果多个线程之间对资源的请求形成了一个循环等待的关系,就可能会发生死锁。
  2. 例子
    • 假设有三个资源 A、B、C,线程 1 持有资源 A,等待资源 B;线程 2 持有资源 B,等待资源 C;线程 3 持有资源 C,等待资源 A。这样,三个线程就形成了一个循环等待的关系,从而导致死锁。
    • 以下是一个用 Java 代码演示循环等待条件导致死锁的例子:
public class CircularWaitDeadlockExample {public static Object resourceA = new Object();public static Object resourceB = new Object();public static Object resourceC = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (resourceA) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resourceB) {System.out.println("Thread 1 acquired both resources.");}}});Thread thread2 = new Thread(() -> {synchronized (resourceB) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resourceC) {System.out.println("Thread 2 acquired both resources.");}}});Thread thread3 = new Thread(() -> {synchronized (resourceC) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resourceA) {System.out.println("Thread 3 acquired both resources.");}}});thread1.start();thread2.start();thread3.start();}
}

在这个例子中,三个线程分别持有一个资源,然后去请求另一个资源,形成了一个循环等待的关系,从而导致死锁。

四、如何避免死锁

(一)破坏互斥条件

  1. 解释
    • 虽然很难完全破坏互斥条件,因为很多资源本身就是天然互斥的,但是在某些特定情况下,可以通过使用资源的共享模式来减少互斥的程度。
    • 例如,对于一些可以同时被多个线程读取的资源,可以使用读写锁来代替普通的互斥锁。这样,多个线程可以同时读取资源,只有在写操作时才需要互斥。
  2. 例子
    • 假设我们有一个共享的计数器资源,多个线程可以同时读取计数器的值,但是只有一个线程可以修改计数器的值。我们可以使用读写锁来实现这个功能:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class AvoidDeadlockByBreakingMutualExclusion {private int counter = 0;private final ReadWriteLock lock = new ReentrantReadWriteLock();public int getCounter() {lock.readLock().lock();try {return counter;} finally {lock.readLock().unlock();}}public void incrementCounter() {lock.writeLock().lock();try {counter++;} finally {lock.writeLock().unlock();}}
}

在这个例子中,多个线程可以同时调用getCounter方法读取计数器的值,因为读操作是共享的。只有当一个线程调用incrementCounter方法修改计数器的值时,才需要互斥。这样就减少了互斥的程度,从而降低了死锁的可能性。

(二)破坏请求与保持条件

  1. 解释
    • 可以要求线程在开始执行之前一次性请求所有需要的资源,而不是在执行过程中逐步请求资源。如果一个线程无法一次性获得所有需要的资源,那么它就应该释放已经获得的资源,然后等待一段时间后再重新尝试。
  2. 例子
    • 假设我们有两个资源 A 和 B,线程需要同时使用这两个资源。我们可以让线程在开始执行之前一次性请求这两个资源,如果无法获得这两个资源,就释放已经获得的资源,然后等待一段时间后再重新尝试:
public class AvoidDeadlockByBreakingRequestAndHold {public static Object resourceA = new Object();public static Object resourceB = new Object();public static void main(String[] args) {Thread thread = new Thread(() -> {while (true) {synchronized (resourceA) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resourceB) {System.out.println("Thread acquired both resources.");break;}}// Release resource A and try again latersynchronized (resourceA) {}}});thread.start();}
}

在这个例子中,线程在获得资源 A 后,如果无法获得资源 B,就会释放资源 A,然后等待一段时间后再重新尝试。这样就避免了请求与保持条件,从而降低了死锁的可能性。

(三)破坏不剥夺条件

  1. 解释
    • 可以设计一种机制,允许在某些情况下强行剥夺一个线程已经获得的资源。但是这种方法比较复杂,并且可能会导致一些问题,所以一般不太常用。
  2. 例子
    • 假设我们有一个资源分配系统,当一个线程长时间持有某个资源而不使用时,系统可以强行剥夺这个资源,并分配给其他需要的线程。为了实现这个功能,我们可以使用一个定时器来检测线程对资源的使用情况,如果一个线程在一定时间内没有使用某个资源,系统就可以强行剥夺这个资源:
import java.util.Timer;
import java.util.TimerTask;public class AvoidDeadlockByBreakingNonPreemption {public static Object resource = new Object();public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {synchronized (resource) {System.out.println("Resource forcibly released.");synchronized (resource) {}}}}, 5000);Thread thread = new Thread(() -> {synchronized (resource) {try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread finished using resource.");}});thread.start();}
}

在这个例子中,定时器在 5 秒后会强行剥夺线程对资源的锁。这样就破坏了不剥夺条件,从而降低了死锁的可能性。但是这种方法需要谨慎使用,因为强行剥夺资源可能会导致一些不可预料的问题。

(四)破坏循环等待条件

  1. 解释
    • 可以对资源进行编号,要求线程按照编号顺序请求资源。这样就可以避免循环等待。
  2. 例子
    • 假设我们有三个资源 A、B、C,我们可以给这三个资源编号为 1、2、3。线程在请求资源时,必须按照编号顺序请求资源。例如,线程如果需要同时使用资源 A 和资源 B,那么它必须先请求资源 A,然后再请求资源 B:
public class AvoidDeadlockByBreakingCircularWait {public static Object resourceA = new Object();public static Object resourceB = new Object();public static Object resourceC = new Object();public static void main(String[] args) {Thread thread = new Thread(() -> {int minResource = Math.min(Math.min(resourceA.hashCode(), resourceB.hashCode()), resourceC.hashCode());int maxResource = Math.max(Math.max(resourceA.hashCode(), resourceB.hashCode()), resourceC.hashCode());Object firstResource = minResource == resourceA.hashCode()? resourceA : (minResource == resourceB.hashCode()? resourceB : resourceC);Object secondResource = minResource == resourceA.hashCode()? (resourceB.hashCode() < resourceC.hashCode()? resourceB : resourceC) :(minResource == resourceB.hashCode()? (resourceA.hashCode() < resourceC.hashCode()? resourceA : resourceC) :(resourceA.hashCode() < resourceB.hashCode()? resourceA : resourceB));synchronized (firstResource) {synchronized (secondResource) {System.out.println("Thread acquired both resources.");}}});thread.start();}
}

在这个例子中,线程按照资源的哈希码大小顺序请求资源,避免了循环等待,从而降低了死锁的可能性。

五、总结

死锁是 Java 多线程编程中一个比较复杂但又非常重要的问题。了解死锁的产生条件以及如何避免死锁,对于编写高效、稳定的多线程程序至关重要。本文详细介绍了 Java 死锁的四个必要条件,即互斥条件、请求与保持条件、不剥夺条件和循环等待条件,并通过具体的例子和解决方案帮助读者更好地理解和避免死锁。在实际编程中,我们应该尽量避免死锁的发生,通过合理的资源管理和线程同步机制,确保程序的稳定性和可靠性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146748.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

SFUD库移植

1.源码 GitHub - armink/SFUD: An using JEDECs SFDP standard serial (SPI) flash universal driver library | 一款使用 JEDEC SFDP 标准的串行 (SPI) Flash 通用驱动库 2.介绍 这个通用驱动库,实际就是帮你封装好了读写spiflash的函数, 我们只需要对接以下底层,就可以轻松…

助力降糖新品“五菌膏”上市 科技特派员秋季行硕果累累

近日&#xff0c;武汉市“科技助力乡村振兴科技特派员秋季行活动”在武汉举行&#xff0c;此次活动由长江新区管委会、市科创局、武汉轻工大学主办&#xff0c;长江新区科技创新与成果转化局、武汉市科技成果转化促进中心、武汉市科技特派员创新联盟承办。 9月19日&#xff0c;…

VM虚拟机下载以及激活

传统的官网已经找不到下载了&#xff0c;这里我将下载好的放在阿里云盘&#xff0c;百度云盘太慢了&#xff0c;懂得都得 阿里云盘分享 下载好了后会是一个exe文件&#xff0c;直接双击运行就可 下载无脑下一步即可&#xff0c;这里不做介绍 下载好了后&#xff0c;需要密钥这里…

免费的AI测肤

AI测肤 https://beifuting.com/

实用好软-----电脑端 全能音视频转换器 转换各种音视频格式

软件介绍&#xff1a; 工具是一款免费的视频格式转换软件&#xff0c;支持几乎所有视频格式的转换&#xff0c;基本的有DVD, AVI, MP4, 3GP, WMV, ASF等格式。对于一些特殊格式的视频&#xff0c;不用担心看不到&#xff0c;除了保证转换质量&#xff0c;还能转换为你想要的类…

[图解]静态关系和动态关系

1 00:00:01,060 --> 00:00:04,370 首先我们来看静态关系和动态关系 2 00:00:06,160 --> 00:00:10,040 我们要尽量基于静态关系来建立动态关系 3 00:00:11,740 --> 00:00:13,740 不能够在没有这个的基础上 4 00:00:14,220 --> 00:00:17,370 没有这个的情况下就胡…

vue3 选择字体的颜色,使用vue3-colorpicker来选择颜色

1、有的时候我们会用到颜色的选择器&#xff0c;像element-plus提供了&#xff0c;但是ant-design-vue并没有&#xff1a; 这个暂时没有看到&#xff1a; 但是Ant Design 5的版本有&#xff0c;应该不是vue的。 2、使用第三方提供的vue3-colorpicker&#xff1a;storybook/cli…

《程序猿之设计模式实战 · 模板方法》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

NodeJs文档

文件操作 // 1. 导入fs模块 const fs require(fs)文件写入 //异步写入 // fs.writeFile(文件名&#xff0c; 待写入的数据&#xff0c; 选项设置&#xff08;可选&#xff09;&#xff0c; 回调函数) fs.writeFile(./座右铭.txt, 三人行&#xff0c;必有我师傅, err > {/…

专注并不意味只做一件事

原创内容第658篇&#xff0c;专注量化投资、个人成长与财富自由。 财务自由本身就是一个很有争议的领域。 有谁能靠别人实现财富自由呢&#xff1f; 这个逻辑起点本身就有问题。 如果预期正确&#xff0c;那这些自媒体还是有用处的。 好比我现在对于阅读和书籍的预期&…

看过来!这水凝胶,机械强、抗冻佳、导电优

大家好&#xff0c;如今智能穿戴设备越来越普及&#xff0c;但传统传感器有不少局限性。比如说&#xff0c;传统水基水凝胶用作柔性传感器材料时&#xff0c;保水性和抗冻性就不太好&#xff0c;这会影响其稳定性和应用范围。那有没有什么办法解决这些问题呢&#xff1f;今天我…

增强现实系列—Real-Time Simulated Avatar from Head-Mounte

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

软考流水线计算

某计算机系统输入/输出采用双缓冲工作方式&#xff0c;其工作过程如下图所示&#xff0c;假设磁盘块与缓冲区大小相同&#xff0c;每个盘块读入缓冲区的时间T为10μs&#xff0c;由缓冲区送至用户区的时间M为6μs&#xff0c;系统对每个磁盘块数据的处理时间C为2μs。若用户需要…

SaaS业务架构:业务能力分析

大家好&#xff0c;我是汤师爷~ 今天聊聊SaaS业务架构的业务能力分析。 业务能力概述 简单来说&#xff0c;业务能力是企业“做某事的能力”。 业务能力描述了企业当前和未来应对挑战的能力&#xff0c;即企业能做什么或需要做什么。业务能力建模的关键在于定义了企业做什么…

linux 安装libreoffice

yum install libreoffice 有点大有一个G

libreoffice word转pdf

一、准备一个word文件 运行&#xff1a; cd /root libreoffice --headless --convert-to pdf --outdir /root/output doc1.docx 发现中文乱码&#xff1a; 此时我们需要给linux 上添加中文字体&#xff1a; centos7 添加中文字体 再次运行正常&#xff1a; libreoffice --h…

中断-MCU

中断 目录 中断 中断的概念 中断的执行过程 中断服务函数 中断的部分专业术语 – 了解 STM32中的中断分类 嵌套向量中断控制器 NVIC STM32中的中断优先级 中断编程 外部中断&#xff08;单片机之外&#xff09;之EXTI中断 相关寄存器 外部中断&#xff08;EXTI&am…

EdgeRoute_镜像烧录

1. EdgeRouter 概述 EdgeRouter Lite 是由 Ubiquiti Networks 公司生产的一款高性能网络路由器&#xff0c;适用于家庭和小型办公环境。它的尺寸为200 x 90 x 30 mm&#xff0c;重量为345克&#xff0c;配备了双核500 MHz的MIPS64处理器&#xff0c;并带有硬件加速功能&#x…

《AI系统:原理与架构》于华为HC大会2024正式发布

2024年9月21日&#xff0c;《AI系统&#xff1a;原理与架构》新书发布会在上海世博馆华为HC大会顺利举办。本书由华为昇腾技术专家、B站AI科普博主ZOMI酱和哈工大软件学院副院长苏统华教授联合编写&#xff0c;是领域内AI系统方面填补空白的重磅之作。 发布会上&#xff0c;《A…

机器人学基础——旋转矩阵转四元数的C++程序实现

一、理论基础 1. 旋转矩阵 旋转矩阵通常是一个3x3矩阵&#xff0c;表示物体的旋转变换。一个标准的旋转矩阵 ( R ) 如下&#xff1a; R ( r 11 r 12 r 13 r 21 r 22 r 23 r 31 r 32 r 33 ) R \begin{pmatrix} r_{11} & r_{12} & r_{13} \\ r_{21} & r_{22} &am…