news 2026/4/23 14:43:17

Java多线程三大困境:死锁、活锁与饥饿的区别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java多线程三大困境:死锁、活锁与饥饿的区别

文章目录

  • Java多线程三大困境:死锁、活锁与饥饿的区别?
    • 一、线程世界的“三大煞星”
      • 1. 死锁(Deadlock)
        • 死锁的形成条件
        • 死锁的经典示例
      • 2. 活锁(Livelock)
        • 活锁的形成
        • 活锁的经典示例
      • 3. 饥饿(Starvation)
        • 饥饿的原因
        • 饥饿的经典示例
    • 二、如何解决这三大困境?
      • 1. 解决死锁
        • 预防死锁的示例
      • 2. 解决活锁
        • 解决不活锁的示例
      • 3. 解决饥饿
        • 使用公平锁的示例
    • 总结
    • 希望这篇文章能帮助你更好地理解这些多线程问题,并写出更健壮的代码!
      • 📚 领取 | 1000+ 套高质量面试题大合集(无套路,闫工带你飞一把)!

Java多线程三大困境:死锁、活锁与饥饿的区别?

大家好,闫工又来啦!今天我们要聊的是Java多线程编程中三个让人头大的问题——死锁活锁饥饿。这三个词听起来都像是线程的“终极 boss”,但实际上它们各有各的特点和解决办法。作为一名Java开发者,我们不仅要认识它们,还要学会如何在代码中避免这些陷阱。


一、线程世界的“三大煞星”

1. 死锁(Deadlock)

死锁是多线程编程中最常见的问题之一。简单来说,死锁是指两个或多个线程互相等待对方释放资源,导致所有相关线程都无法继续执行。这种情况下,程序会陷入停滞状态,就像两辆车在十字路口对峙,谁都不愿意先让开。

死锁的形成条件

要理解死锁,我们需要知道它的四个必要条件:

  1. 互斥条件:每个资源只能被一个线程占用。
  2. 不可剥夺条件:线程不能强行释放资源,必须主动释放。
  3. 请求和保持条件:线程在等待其他资源时,不会释放已经占有的资源。
  4. 循环等待条件:存在一组线程,每个线程都在等待下一个线程释放的资源。
死锁的经典示例

下面是一个经典的死锁代码示例:

publicclassDeadlockExample{publicstaticvoidmain(String[]args){// 创建两个对象作为资源Objectlock1=newObject();Objectlock2=newObject();// 线程A:先获取lock1,再获取lock2ThreadthreadA=newThread(()->{synchronized(lock1){System.out.println("Thread A has lock1");try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized(lock2){System.out.println("Thread A has both locks");}}});// 线程B:先获取lock2,再获取lock1ThreadthreadB=newThread(()->{synchronized(lock2){System.out.println("Thread B has lock2");try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized(lock1){System.out.println("Thread B has both locks");}}});threadA.start();threadB.start();}}

在这个例子中,线程A和线程B可能会同时持有不同的锁并等待对方的资源,最终导致死锁。


2. 活锁(Livelock)

活锁是另一种多线程问题。活锁是指线程不断尝试获取资源,但由于某种原因总是失败,从而无限循环而无法继续执行。与死锁不同,活锁中的线程仍然在运行,但它们无法完成任何有意义的工作。

活锁的形成

活锁通常发生在以下情况:

  1. 线程A和线程B都试图获取资源。
  2. 两者同时检测到冲突,然后尝试回退(比如释放已占用的资源)并重新开始。
  3. 如果它们不断重复这个过程,就会陷入活锁。
活锁的经典示例

下面是一个简单的活锁示例:

publicclassLivelockExample{publicstaticvoidmain(String[]args){// 创建两个线程,尝试同时获取资源ThreadthreadA=newThread(()->{while(true){synchronized(LivelockExample.class){System.out.println("Thread A is trying to get the lock");if(Math.random()<0.5){System.out.println("Thread A releases the lock");continue;// 释放锁并重新尝试}else{return;}}}});ThreadthreadB=newThread(()->{while(true){synchronized(LivelockExample.class){System.out.println("Thread B is trying to get the lock");if(Math.random()<0.5){System.out.println("Thread B releases the lock");continue;// 释放锁并重新尝试}else{return;}}}});threadA.start();threadB.start();}}

在这个例子中,两个线程可能会不断释放和重新获取锁,导致活锁。


3. 饥饿(Starvation)

饥饿是多线程编程中的另一个常见问题。饥饿是指某些线程由于优先级或其他原因,长时间得不到 CPU 的调度或资源的访问机会,从而无法完成任务

饥饿的原因

饥饿通常发生在以下情况:

  1. 优先级倒置:高优先级线程占据了资源,导致低优先级线程无法获取资源。
  2. 资源分配策略不合理:某些线程总是被“插队”,无法公平地获得资源。
饥饿的经典示例

下面是一个饥饿的示例:

publicclassStarvationExample{publicstaticvoidmain(String[]args){// 创建两个线程,一个高优先级,一个低优先级ThreadthreadA=newThread(()->{while(true){synchronized(StarvationExample.class){System.out.println("Thread A is running");try{Thread.sleep(100);}catch(InterruptedExceptione){}}}},"HighPriority");// 降低线程A的优先级threadA.setPriority(Thread.MIN_PRIORITY);ThreadthreadB=newThread(()->{while(true){synchronized(StarvationExample.class){System.out.println("Thread B is running");try{Thread.sleep(100);}catch(InterruptedExceptione){}}}},"LowPriority");// 提高线程B的优先级threadB.setPriority(Thread.MAX_PRIORITY);threadA.start();threadB.start();}}

在这个例子中,由于线程B的优先级更高,它可能会一直占用资源,导致线程A长时间得不到执行。


二、如何解决这三大困境?

1. 解决死锁

要避免死锁,我们需要打破其中一个必要条件。以下是几种常见的方法:

  • 预防:通过设计,避免同时获取多个锁。
  • 检测和恢复:在代码中定期检查是否发生了死锁,并采取措施恢复。
预防死锁的示例
publicclassDeadlockPrevention{publicstaticvoidmain(String[]args){// 确保所有线程都按相同的顺序获取锁ThreadthreadA=newThread(()->{synchronized(DeadlockPrevention.class){System.out.println("Thread A has lock1");try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized("string"){System.out.println("Thread A has both locks");}}});ThreadthreadB=newThread(()->{synchronized(DeadlockPrevention.class){System.out.println("Thread B has lock1");try{Thread.sleep(100);}catch(InterruptedExceptione){}synchronized("string"){System.out.println("Thread B has both locks");}}});threadA.start();threadB.start();}}

2. 解决活锁

要解决活锁,我们需要让线程在尝试获取资源时有一定的延迟或随机等待时间,以避免无限循环。

解决不活锁的示例
publicclassLivelockSolution{publicstaticvoidmain(String[]args){ThreadthreadA=newThread(()->{while(true){synchronized(LivelockSolution.class){System.out.println("Thread A is trying to get the lock");if(Math.random()<0.5){try{Thread.sleep(100);}catch(InterruptedExceptione){}continue;}else{return;}}}});ThreadthreadB=newThread(()->{while(true){synchronized(LivelockSolution.class){System.out.println("Thread B is trying to get the lock");if(Math.random()<0.5){try{Thread.sleep(100);}catch(InterruptedExceptione){}continue;}else{return;}}}});threadA.start();threadB.start();}}

3. 解决饥饿

要解决饥饿,我们需要确保所有线程都能公平地获得资源。以下是几种常见的方法:

  • 使用公平锁:Java 提供了ReentrantLock的公平模式。
  • 调整优先级策略:避免优先级倒置。
使用公平锁的示例
importjava.util.concurrent.locks.ReentrantLock;publicclassStarvationSolution{privatestaticReentrantLocklock=newReentrantLock(true);// 公平锁publicstaticvoidmain(String[]args){ThreadthreadA=newThread(()->{while(true){try{if(lock.tryLock()){System.out.println("Thread A acquired the lock");try{Thread.sleep(100);}catch(InterruptedExceptione){}lock.unlock();}}catch(Exceptione){}}},"HighPriority");threadA.setPriority(Thread.MIN_PRIORITY);ThreadthreadB=newThread(()->{while(true){try{if(lock.tryLock()){System.out.println("Thread B acquired the lock");try{Thread.sleep(100);}catch(InterruptedExceptione){}lock.unlock();}}catch(Exceptione){}}},"LowPriority");threadB.setPriority(Thread.MAX_PRIORITY);threadA.start();threadB.start();}}

总结

死锁、活锁和饥饿是多线程编程中的三大困境,但通过合理的设计和代码优化,我们可以有效地避免这些问题。记住以下几点:

  1. 预防死锁:确保所有线程按相同的顺序获取锁。
  2. 解决活锁:引入随机延迟或使用公平锁。
  3. 避免饥饿:使用公平锁并调整优先级策略。

希望这篇文章能帮助你更好地理解这些多线程问题,并写出更健壮的代码!

📚 领取 | 1000+ 套高质量面试题大合集(无套路,闫工带你飞一把)!

成体系的面试题,无论你是大佬还是小白,都需要一套JAVA体系的面试题,我已经上岸了!你也想上岸吗?

闫工精心准备了程序准备面试?想系统提升技术实力?闫工精心整理了1000+ 套涵盖前端、后端、算法、数据库、操作系统、网络、设计模式等方向的面试真题 + 详细解析,并附赠高频考点总结、简历模板、面经合集等实用资料!

✅ 覆盖大厂高频题型
✅ 按知识点分类,查漏补缺超方便
✅ 持续更新,助你拿下心仪 Offer!

📥免费领取👉 点击这里获取资料

已帮助数千位开发者成功上岸,下一个就是你!✨

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 14:37:48

Spark命令行工具终极指南:5个数据可视化技巧快速上手

Spark命令行工具终极指南&#xff1a;5个数据可视化技巧快速上手 【免费下载链接】spark ▁▂▃▅▂▇ in your shell. 项目地址: https://gitcode.com/gh_mirrors/spark/spark 在数据分析和系统监控的日常工作中&#xff0c;数据可视化是理解复杂信息的关键环节。Spar…

作者头像 李华
网站建设 2026/4/23 12:37:28

安卓设备终极清理指南:一键卸载预装应用提升性能

安卓设备终极清理指南&#xff1a;一键卸载预装应用提升性能 【免费下载链接】universal-android-debloater-next-generation Cross-platform GUI written in Rust using ADB to debloat non-rooted Android devices. Improve your privacy, the security and battery life of …

作者头像 李华
网站建设 2026/4/23 14:38:58

终极Markdown幻灯片制作工具:Marp完全指南

终极Markdown幻灯片制作工具&#xff1a;Marp完全指南 【免费下载链接】marp The site of classic Markdown presentation writer app 项目地址: https://gitcode.com/gh_mirrors/ma/marp 想要用最简单的方式制作专业幻灯片吗&#xff1f;Marp正是你需要的解决方案&…

作者头像 李华
网站建设 2026/4/23 14:40:45

40、服务器性能监控与优化全解析

服务器性能监控与优化全解析 在服务器管理和运维过程中,性能监控与优化是至关重要的环节。它能够帮助我们及时发现服务器运行中的问题,确保服务器的稳定运行和高效性能。以下将详细介绍如何对服务器的内存和存储性能进行监控与分析。 1. 内核内存分析 为了深入了解内核的工…

作者头像 李华
网站建设 2026/4/22 17:55:03

45、Bash Shell脚本:变量与输入处理全解析

Bash Shell脚本:变量与输入处理全解析 在Bash Shell脚本编程中,变量和输入的处理是非常重要的部分。下面将详细介绍如何在脚本中使用参数、获取用户输入、进行命令替换以及使用各种操作符来处理变量。 脚本参数的使用 在运行脚本时,可以在命令行指定参数。在脚本中,可以…

作者头像 李华
网站建设 2026/4/23 6:03:28

46、Bash脚本计算与控制结构全解析

Bash脚本计算与控制结构全解析 1. Bash脚本中的计算方法 在Bash脚本里,能够进行简单计算,尽管无法替代电子表格程序,但在特定场景下很实用,比如多次执行命令或者确保命令成功执行时计数器自增。下面为你详细介绍几种计算方法。 1.1 使用计数器示例 以下是一个简单的计数…

作者头像 李华