当前位置: 首页 > wzjs >正文

绵阳做网站哪家公司好chatgpt网站

绵阳做网站哪家公司好,chatgpt网站,无锡网站建设团队,wordpress文章显示失败在多线程编程的世界里,我们追求利用并发来提高程序的性能和响应速度。然而,并发也带来了一系列挑战,其中最令人头疼的问题之一就是——死锁 (Deadlock)。一旦发生死锁,程序可能完全“卡住”,无法继续执行,对…

在多线程编程的世界里,我们追求利用并发来提高程序的性能和响应速度。然而,并发也带来了一系列挑战,其中最令人头疼的问题之一就是——死锁 (Deadlock)。一旦发生死锁,程序可能完全“卡住”,无法继续执行,对系统稳定性和用户体验造成严重影响。

本文将带你深入理解什么是死锁,并通过一个经典的 Java 代码示例来直观演示死锁是如何发生的,最后探讨如何预防或避免死锁。

什么是死锁?

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或全部都在等待某个只有其它阻塞线程才能释放的资源。由于线程被无限期地阻塞,程序无法正常终止,也无法继续执行后续任务。

想象一个简单的场景:有两条单行道在一个狭窄的路口交汇,每条道上都有一辆车想要通过路口到达对方的道路。如果两辆车同时驶入路口,互相挡住了对方的去路,并且谁也不愿意后退(释放路口资源),那么这两辆车就陷入了“死锁”状态,交通完全瘫痪。

在多线程环境中,资源通常指的就是锁 (Lock),例如 Java 中的 synchronized​ 关键字或者 ReentrantLock​ 对象。

图解经典死锁场景

一个非常典型的死锁场景涉及两个线程和两个资源:

  • 线程 A 持有 资源 1,并尝试获取 资源 2。
  • 线程 B 持有 资源 2,并尝试获取 资源 1。

如下图所示:

graph LR A((线程 A)) -- 持有 --> R1(资源 1); B((线程 B)) -- 持有 --> R2(资源 2); A -- 等待 --> R2; B -- 等待 --> R1;

由于线程 A 必须等待线程 B 释放资源 2,而线程 B 又必须等待线程 A 释放资源 1,两者互相等待,形成了一个循环依赖,谁也无法前进,死锁就此产生。

Java 代码示例:模拟死锁

下面的 Java 代码精确地模拟了上述的死锁情况:

public class DeadLockDemo {// 共享资源 1 (使用 Object 作为锁对象)private static final Object resource1 = new Object();// 共享资源 2private static final Object resource2 = new Object();public static void main(String[] args) {// 线程 1new Thread(() -> {// 1. 线程 1 获取 resource1 的锁synchronized (resource1) {System.out.println(Thread.currentThread().getName() + " 获取 resource1");try {// 2. 休眠 1 秒,给线程 2 足够的时间去获取 resource2 的锁Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 恢复中断状态e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 等待获取 resource2...");// 3. 线程 1 尝试获取 resource2 的锁 (此时 resource2 可能已被线程 2 持有)synchronized (resource2) {System.out.println(Thread.currentThread().getName() + " 获取 resource2");}}System.out.println(Thread.currentThread().getName() + " 执行完毕"); // 如果发生死锁,这行不会打印}, "线程 1").start();// 线程 2new Thread(() -> {// 4. 线程 2 获取 resource2 的锁synchronized (resource2) {System.out.println(Thread.currentThread().getName() + " 获取 resource2");try {// 5. 休眠 1 秒,给线程 1 足够的时间去尝试获取 resource2Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 等待获取 resource1...");// 6. 线程 2 尝试获取 resource1 的锁 (此时 resource1 已被线程 1 持有)synchronized (resource1) {System.out.println(Thread.currentThread().getName() + " 获取 resource1");}}System.out.println(Thread.currentThread().getName() + " 执行完毕"); // 如果发生死锁,这行不会打印}, "线程 2").start();System.out.println("主线程启动子线程完毕");}
}

代码执行流程分析:

  1. ​线程 1​ 启动,获取 resource1​ 的锁,打印信息,然后休眠。
  2. 在 线程 1​ 休眠期间,线程 2​ 启动,获取 resource2​ 的锁,打印信息,然后休眠。
  3. ​线程 1​ 醒来,尝试获取 resource2​ 的锁。但是 resource2​ 的锁正被 线程 2​ 持有,所以 线程 1​ 进入阻塞状态,等待 线程 2​ 释放 resource2​。
  4. ​线程 2​ 醒来,尝试获取 resource1​ 的锁。但是 resource1​ 的锁正被 线程 1​ 持有,所以 线程 2​ 也进入阻塞状态,等待 线程 1​ 释放 resource1​。

此时,线程 1​ 等待 线程 2​,线程 2​ 等待 线程 1​,形成循环等待,死锁发生!程序的输出会停留在:

线程 1 获取 resource1
线程 2 获取 resource2
线程 1 等待获取 resource2...
线程 2 等待获取 resource1...

后续的 "获取 resource2"、"获取 resource1" 以及 "执行完毕" 的信息将永远不会打印出来。

死锁产生的四个必要条件

死锁的发生并非偶然,它需要同时满足以下四个条件(也被称为 Coffman 条件):

  1. 互斥条件 (Mutual Exclusion): 资源不能被共享,一次只能被一个线程使用。 (示例中 synchronized​ 保证了这一点)。
  2. 请求与保持条件 (Hold and Wait): 线程至少已经持有了一个资源,并且正在请求获取其他线程持有的资源。(示例中,线程 1 持有 R1 等待 R2,线程 2 持有 R2 等待 R1)。
  3. 不可剥夺条件 (No Preemption): 资源只能由持有它的线程自愿释放,不能被其他线程强行剥夺。(示例中,线程不会主动放弃已获得的锁)。
  4. 循环等待条件 (Circular Wait): 存在一个线程资源的循环等待链,即线程 T1 等待 T2 的资源,T2 等待 T3 的资源,...,Tn 等待 T1 的资源。(示例中形成了 T1 -> R2 -> T2 -> R1 -> T1 的循环)。

只有当这四个条件同时满足时,才会发生死锁。 因此,预防死锁的策略就是尝试破坏其中一个或多个条件。

如何预防和避免死锁?

知道了死锁的成因,我们就可以采取相应的措施来避免它:

  1. 破坏“请求与保持”条件:

    • 一次性申请所有资源: 线程在开始执行前,尝试一次性获取所有需要的资源。如果无法全部获取,则不持有任何资源,稍后重试。这种方式可能导致资源利用率降低和线程饥饿。
    • 获取不到即释放: 线程在尝试获取另一个资源失败时,主动释放自己当前持有的资源。
  2. 破坏“不可剥夺”条件:

    • 使用可中断锁或超时锁: Java 的 Lock​ 接口提供了 tryLock(long time, TimeUnit unit)​ 和 lockInterruptibly()​ 等方法。如果一个线程在指定时间内无法获取锁,或者在等待时被中断,它可以选择放弃获取,并可能释放已持有的锁,从而打破循环。
  3. 破坏“循环等待”条件(最常用):

    • 按序申请资源: 对所有共享资源进行排序,并规定所有线程必须按照这个固定的顺序来申请资源。例如,在我们的示例中,可以规定所有线程必须先申请 resource1​,再申请 resource2​。这样,即使 线程 1​ 持有 resource1​,线程 2​ 也必须先尝试获取 resource1​,此时它会被阻塞,但不会持有 resource2​,线程 1​ 最终能获取到 resource2​,完成后释放 resource1​,线程 2​ 就能继续执行。

    修复示例代码 (按序申请):

    // 让线程 2 也先尝试获取 resource1
    new Thread(() -> {synchronized (resource1) { // 先获取 R1System.out.println(Thread.currentThread().getName() + " 获取 resource1");try {Thread.sleep(1000); // 模拟耗时} catch (InterruptedException e) { /* ... */ }System.out.println(Thread.currentThread().getName() + " 等待获取 resource2...");synchronized (resource2) { // 再获取 R2System.out.println(Thread.currentThread().getName() + " 获取 resource2");}}System.out.println(Thread.currentThread().getName() + " 执行完毕");
    }, "线程 2").start();
    

    通过强制所有线程按 resource1 -> resource2​ 的顺序获取锁,循环等待条件被打破,死锁就不会发生。

  4. 死锁检测与恢复 (不常用):

    • 这是一种更复杂的方法,通常在数据库系统或操作系统层面实现。系统允许死锁发生,但会周期性地检测是否存在死锁环,如果检测到,则采取策略进行恢复,例如强制终止(剥夺)某个线程的资源或回滚事务。

总结

死锁是并发编程中一个潜在的严重问题,它源于多个线程对共享资源的竞争和不当的获取顺序。理解死锁的定义、产生的四个必要条件是诊断和预防死锁的基础。在实际开发中,最常用且有效的预防策略是按序申请资源,打破循环等待条件。同时,合理使用 JUC 包提供的锁工具(如 tryLock​)也能帮助我们构建更健壮、更能避免死锁的并发程序。

编写并发代码时,时刻警惕可能发生的死锁,并采取适当的预防措施,是每一位开发者都需要具备的重要技能。

http://www.dtcms.com/wzjs/138196.html

相关文章:

  • 商城项目seo入门教程
  • 网站建设典型发言黄页88
  • 劫持网站代做排名营销推广技巧
  • 我要做一个网站 需要营业范围吗网络营销品牌案例
  • 做企业网站用二级域名好吗百度推广客服投诉电话
  • 呼和浩特重大消息宁波seo运营推广平台排名
  • 金科科技 做网站电脑课程培训零基础
  • 深圳做网站排名哪家好网站页面的优化
  • 闲鱼怎么做钓鱼网站企业网站seo方案
  • 湖北武汉网站建设演艺搜索引擎查询
  • 初学者拟建网站朋友圈广告30元 1000次
  • 企业网站建设可行性网络整合营销理论
  • 山东旗舰建设集团网站海南网站网络推广
  • 顺德网站建seo关键词优化软件
  • 东莞企石做网站百度关键词搜索排名统计
  • 一台电脑如何做网站免费浏览网站推广
  • 网站建设单选题做百度推广的公司电话号码
  • 大连网站建设信息武汉seo关键词排名优化
  • 个人网站可以做哪些主题网盘资源免费观看
  • 做地铁建设的公司网站杭州seo软件
  • 鄂尔多斯网站制作公司我要登录百度
  • 云服务器和网站备案吗seo标题优化关键词怎么选
  • 长沙 网站设计 公司抖音seo什么意思
  • 做微信小程序网站国内做网站的公司
  • 网站价格全网营销推广公司
  • 网站title标签内容怎么设置南京seo网站管理
  • 吉林省人民政府门户网站首页关键词排名
  • 适合写论文的中小企业名录佛山抖音seo
  • 怎么做扫二维码登陆网站今天的新闻发布会
  • 深圳网站制作易捷网络2023年6月疫情情况