多线程之死锁

简介: 多线程之死锁

死锁是在我们的程序中出现了程序的嵌套锁导致的

死锁是一个错误

是多线程中很重要的一共知识点

示意图

B等着A释放锁

A等着B释放锁

介绍线程

线程是操作系统能够进行运算调度的最小单位,他被包含在进程之内,是进程中实际运作的单位

每一个线程都有独立的代码和数据空间

线程在执行代码的时候

线程在执行的时候 具有随机性

CPU的执行权随时有可能被其他的线程抢走

线程里有一个概念叫锁

当有线程进入时 锁会自动关闭

线程出去后锁会自动打开

如果有线程先拿到锁的对象 获取了CPU的执行权

其他的线程就算拿到CPU的执行权也会被关在外面

其他线程抢到CPU执行权后会发现无法获得锁的对象

此时锁已经关闭

死锁就是线程1,2各自拿到了锁A,B的对象

线程1,2得获取锁B,A的对象才能进入下一步

但是需要的锁对象被对方已经拿到了

代码实现

public class MyThread extends Thread{
    //创建锁的对象
    static Object objA=new Object();
    static Object objB=new Object();
 
    @Override
    public void run() {
        if("线程A".equals(getName())){
            synchronized (objA){
                System.out.println("线程A拿到了A锁");
                synchronized (objB){
                    System.out.println("线程A拿到B锁,执行完毕");
                }
            }
        }
        else if("线程B".equals(getName())){
            synchronized (objB){
                System.out.println("线程B拿到了B锁");
                synchronized (objA){
                    System.out.println("线程B拿到了A锁,执行完毕");
                }
            }
        }
    }
}
public class Main {
    public static void main(String[] args) {
 
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
 
        t1.setName("线程A");
        t2.setName("线程B");
 
        t1.start();
        t2.start();
    }
}

控制台显示

程序未停止运行

避免死锁的方法

  1. 加锁顺序: 确定所有线程获取锁的顺序,然后要求所有线程都按照相同的顺序获取锁。
  2. 超时机制: 确定一个超时时间,在尝试获取锁之后,如果超过一定时间还未能成功获取锁,则放弃当前的锁并释放已经获取的锁。
  3. 资源分配策略: 采用预防性分配资源策略,以最大限度地减少发生死锁的可能性。
  4. 死锁检测: 建立一个机制来检测系统中是否存在死锁,一旦检测到死锁,就采取相应的措施来解除死锁。
  5. 避免环路等待: 确保不存在循环等待的情况,例如通过给资源编号,要求线程必须按照递增的顺序请求资源。

银行家算法

一种操作系统基本算法

有人去银行借钱 应该说出借的钱的数量

而银行会根据借的数量 结合自己银行所拥有的财产放出借的钱

银行会在自己的能里范围内最大满足顾客的需求

现在有进程要进入系统 抢占CPU 系统会分析给予资源分配 否则让其等

银行家算法基本原理如下:

  1. 系统在运行时会为每个进程和资源分配一个最大需求量和当前已分配量。
  2. 当一个进程请求资源时,系统会先检查是否有足够的资源可以分配给该进程,如果有,则会尝试分配资源给该进程并检查是否会导致系统进入不安全状态。
  3. 如果分配资源给该进程不会导致系统进入不安全状态,那么就会分配资源给该进程;否则,系统会拒绝分配资源,直到系统处于安全状态。

通过使用银行家算法,系统可以有效地避免死锁情况的发生,保证系统资源的合理利用。

在这段代码中,首先用户需要输入进程数和资源数,然后输入最大需求矩阵、已分配矩阵和可用资源向量。接着程序会根据用户输入计算出需求矩阵。然后通过银行家算法的安全性检查,判断系统是否处于安全状态。

在 isSafe 方法中,程序会模拟系统对进程的资源请求和释放,并通过检查每次操作后系统是否仍然处于安全状态来判断系统整体是否处于安全状态。如果系统可以满足所有进程的资源请求并且不会发生死锁,则系统被认为是处于安全状态。

最后,在 main 方法中调用 isSafe 方法,根据返回的结果输出相应的信息,告知用户系统当前的安全状态。

import java.util.Scanner;
 
public class BankersAlgorithm {
 
    private int[][] maximum; // 最大需求矩阵
    private int[][] allocation; // 已分配矩阵
    private int[][] need; // 需求矩阵
    private int[] available; // 可用资源向量
    private int numberOfProcesses; // 进程数
    private int numberOfResources; // 资源数
 
    // 初始化资源信息
    public void initializeData() {
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("Enter number of processes:");
        numberOfProcesses = scanner.nextInt();
        
        System.out.println("Enter number of resources:");
        numberOfResources = scanner.nextInt();
        
        maximum = new int[numberOfProcesses][numberOfResources];
        allocation = new int[numberOfProcesses][numberOfResources];
        need = new int[numberOfProcesses][numberOfResources];
        available = new int[numberOfResources];
 
        // 输入最大需求矩阵
        System.out.println("Enter maximum matrix:");
        for (int i = 0; i < numberOfProcesses; i++) {
            for (int j = 0; j < numberOfResources; j++) {
                maximum[i][j] = scanner.nextInt();
            }
        }
 
        // 输入已分配矩阵
        System.out.println("Enter allocation matrix:");
        for (int i = 0; i < numberOfProcesses; i++) {
            for (int j = 0; j < numberOfResources; j++) {
                allocation[i][j] = scanner.nextInt();
                // 计算需求矩阵
                need[i][j] = maximum[i][j] - allocation[i][j];
            }
        }
 
        // 输入可用资源向量
        System.out.println("Enter available vector:");
        for (int i = 0; i < numberOfResources; i++) {
            available[i] = scanner.nextInt();
        }
        
        scanner.close();
    }
 
    // 银行家算法检查系统是否处于安全状态
    public boolean isSafe() {
        boolean[] finish = new boolean[numberOfProcesses];
        int[] work = new int[numberOfResources];
        
        // 初始化 work 数组
        for (int i = 0; i < numberOfResources; i++) {
            work[i] = available[i];
        }
 
        // 初始化 finish 数组
        for (int i = 0; i < numberOfProcesses; i++) {
            finish[i] = false;
        }
 
        int count = 0;
        while (count < numberOfProcesses) {
            boolean found = false;
            for (int i = 0; i < numberOfProcesses; i++) {
                if (!finish[i]) {
                    int j;
                    for (j = 0; j < numberOfResources; j++) {
                        if (need[i][j] > work[j]) {
                            break;
                        }
                    }
                    if (j == numberOfResources) {
                        // 如果进程可以被满足
                        for (int k = 0; k < numberOfResources; k++) {
                            work[k] += allocation[i][k];
                        }
                        finish[i] = true;
                        found = true;
                        count++;
                    }
                }
            }
            if (!found) {
                return false; // 无法满足某个进程的需求
            }
        }
        return true; // 所有进程均可以满足
    }
 
    public static void main(String[] args) {
        BankersAlgorithm bankersAlgorithm = new BankersAlgorithm();
        bankersAlgorithm.initializeData();
        if (bankersAlgorithm.isSafe()) {
            System.out.println("System is in safe state.");
        } else {
            System.out.println("System is in unsafe state.");
        }
    }
}


目录
相关文章
|
1月前
|
Java 数据库连接 数据库
不同业务使用同一个线程池发生死锁的技术探讨
【10月更文挑战第6天】在并发编程中,线程池是一种常用的优化手段,用于管理和复用线程资源,减少线程的创建和销毁开销。然而,当多个不同业务场景共用同一个线程池时,可能会引发一系列并发问题,其中死锁就是最为严重的一种。本文将深入探讨不同业务使用同一个线程池发生死锁的原因、影响及解决方案,旨在帮助开发者避免此类陷阱,提升系统的稳定性和可靠性。
42 5
|
1月前
|
安全 Java 程序员
【多线程-从零开始-肆】线程安全、加锁和死锁
【多线程-从零开始-肆】线程安全、加锁和死锁
42 0
|
3月前
|
安全 算法 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
75 6
|
3月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
83 5
|
3月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
80 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) 实施超时和重试机制,减少资源占用。这些改进提高了系统稳定性和用户体验。
207 2
|
5月前
|
Java
在Java中,死锁是指两个或多个线程互相等待对方释放资源,从而导致所有线程都无法继续执行的情况。
【6月更文挑战第24天】在Java并发中,死锁是多线程互相等待资源导致的僵局。避免死锁的关键策略包括:防止锁嵌套,设定固定的加锁顺序,使用`tryLock`带超时,避免无限等待,减少锁的持有时间,利用高级同步工具如`java.util.concurrent`,以及实施死锁检测和恢复机制。通过这些方法,可以提升程序的并发安全性。
42 1