Java零基础之多线程篇:讲解死锁和资源管理

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

  在前几期中,我们基本已经对多线程有了一定的基础及实践。在多线程编程中,有一种情况,我们基本也能遇到,那就是死锁,对于死锁它也是一种常见的问题。当多个线程同时竞争资源时,如果它们互相等待对方释放资源,就会导致死锁的发生。本文将介绍死锁的概念以及如何在Java中进行资源管理来避免死锁。

摘要

  本文主要探讨了Java多线程中死锁的问题和资源管理的方法。首先对死锁的概念进行了解释,并介绍了死锁的必要条件。然后,通过实际的代码示例来展示死锁是如何发生的。接下来,介绍了几种常见的资源管理策略,包括避免死锁、检测死锁和解除死锁。最后,通过一个应用场景案例来说明如何使用资源管理策略来避免死锁的发生。

简介

  死锁是指两个或多个线程互相等待对方释放资源,导致程序无法继续执行的状态。死锁的发生是由于多个线程同时竞争资源,而资源的访问顺序不当引起的。在Java中,使用synchronized关键字可以实现对资源的互斥访问,但如果不正确地使用锁,就会导致死锁的发生。

源代码解析

以下是一个简单的死锁示例代码:

package com.example.javase.ms.threadDemo.day9;

/**
 * @Author ms
 * @Date 2024-04-13 19:51
 */
public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread1 acquired lock1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("Thread1 acquired lock2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread2 acquired lock2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("Thread2 acquired lock1");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

  在上面的代码中,演示了一个典型的死锁情况。在Java中,死锁发生在两个或多个线程互相等待对方释放锁,而这些锁又被对方持有,导致无法继续执行的情况。

  在这个例子中,thread1首先获取lock1,然后等待1秒钟,之后尝试获取lock2。同样,thread2首先获取lock2,然后等待1秒钟,之后尝试获取lock1。如果thread1thread2几乎同时运行,它们可能会同时持有一个锁并等待另一个锁,这将导致死锁。

  代码执行的结果是,两个线程都会在尝试获取第二个锁的时候陷入等待状态,因为每个线程都在等待另一个线程释放它需要的锁。这将导致程序停止响应,因为没有线程能够继续执行。

  具体的输出可能如下所示,但实际的输出可能会因为线程调度的不确定性而有所不同:

Thread1 acquired lock1
Thread2 acquired lock2

  之后,两个线程都会在等待对方释放锁的地方停止,没有进一步的输出。

  为了避免死锁,其实可采取的措施有很多,比如:

  1. 锁排序:为所有的锁分配一个固定的顺序,并确保每个线程都按照这个顺序获取锁。
  2. 锁尝试:尝试获取锁,如果无法获取,则释放已持有的锁并稍后重试。
  3. 使用java.util.concurrent.locks包中的锁:这个包提供了一些可重入锁和条件同步,可以帮助避免死锁。

  在实际开发中,应该尽量避免死锁的发生,因为死锁会导致程序无法继续执行,而且可能很难诊断和解决。

应用场景案例

  假设现在有一个银行账户管理系统,每个账户都有一个账户号和余额。该系统允许多个线程同时对不同账户进行操作,但同一个账户不能同时被多个线程操作。为了避免死锁的发生,我们可以采用以下资源管理策略:

  1. 使用一个全局的锁对象来控制对账户的互斥访问。
  2. 在对账户进行操作之前,先检查该账户是否已经被其他线程锁定,如果是,则等待。
  3. 操作完毕后,释放锁定,并通知其他等待线程可以尝试获取锁。

  通过以上策略的应用,可以有效地避免死锁的发生。

优缺点分析

  资源管理策略的优点是可以避免死锁的发生,确保程序的正常执行。同时,它也能够提高多线程程序的性能,减少线程等待时间。

  然而,资源管理策略也存在一些缺点。首先,需要对每个涉及到资源互斥访问的地方进行修改,增加了代码的复杂性。其次,在高并发环境下,大量的线程等待锁的释放可能导致性能下降。

类代码方法介绍

  在上面的代码示例中,我们使用了synchronized关键字来实现资源的互斥访问。synchronized关键字可以应用于方法和代码块。

  • synchronized方法:将synchronized关键字应用于方法上,表示该方法是一个互斥方法,同一时间只能有一个线程执行该方法。
  • synchronized代码块:将synchronized关键字应用于代码块上,表示该代码块是一个互斥区域,同一时间只能有一个线程执行该代码块。

在实际开发中,根据不同的需求和性能要求,选择合适的方式来进行资源的互斥访问。

测试用例

  在上面的源代码解析中已经提供了一个死锁示例代码。这里我们本地来实际测试一下,通过debug模式,一步一步执行去体验,它究竟是如何死锁并且如何修改?

测试代码

package com.example.javase.ms.threadDemo.day9;

/**
 * @Author ms
 * @Date 2024-04-13 19:51
 */
public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread1 acquired lock1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("Thread1 acquired lock2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread2 acquired lock2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("Thread2 acquired lock1");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

  为了避免死锁的发生,你可以尝试修改示例代码中的锁的获取顺序,或者采用资源管理策略中的方法进行改进。

测试结果展示

  根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

实际的输出与预期结果是一致的,具体可对应上截图。

Thread1 acquired lock1
Thread2 acquired lock2

测试代码分析

  根据如上代码作出解析,以便于同学们更好的理解,分析如下:该测试用例演示了一个死锁的情况。在main方法中,创建了两个线程thread1和thread2。线程thread1首先获取lock1锁,然后休眠1秒钟。接着,线程thread1尝试获取lock2锁。与此同时,线程thread2首先获取lock2锁,然后休眠1秒钟。接着,线程thread2尝试获取lock1锁。

  由于线程thread1占用了lock1锁并等待获取lock2锁,而线程thread2占用了lock2锁并等待获取lock1锁,这就造成了死锁。两个线程都在等待对方释放锁,导致程序无法继续执行。

  解决死锁问题的方法是合理地设计锁的获取顺序,避免出现循环等待的情况。

全文小结

  本文主要介绍了Java中死锁的问题和资源管理的方法。我们首先了解了死锁的概念和必要条件,然后通过实际的代码示例展示了死锁的发生。接下来,介绍了几种常见的资源管理策略,并通过一个应用场景案例说明了如何使用这些策略来避免死锁。最后,针对资源管理策略的优缺点进行了分析。

总结

  死锁呢,是多线程编程中常见的问题,合理的资源管理策略可以有效避免死锁的发生,我们也需要在开发中进行合理的设计及避免死锁。在Java中,可以使用synchronized关键字来实现资源的互斥访问。合理地使用锁对象,并避免资源的互相依赖性,可以最大程度地减少死锁的发生。因此,在多线程编程中,合理的资源管理策略非常重要。

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值