解决死锁问题

解决死锁

死锁是指两个或多个线程在执行过程中,由于竞争资源或者互相等待释放资源而造成的一种僵局,使得所有参与的线程无法继续执行。举个例子,当线程A持有锁a并尝试获取锁b,线程B持有锁b并尝试获取锁a时,就会出现死锁。简单来说,死锁问题的产生是由两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。

排查过程

死锁经常表现为程序的停顿,或者不再响应用户的请求。从操作系统上观察,对应进程的CPU占用率为零,很快会从top的输出中消失。模拟死锁代码:

public class MainTest {

    public static void main(String[] args) {
         String lockA = "lockA";
         String lockB = "lockB";
        new Thread(new ThreadHolderLock(lockA,lockB),"线程AAA").start();
        new Thread(new ThreadHolderLock(lockB,lockA),"线程BBB").start();
    }
}

class ThreadHolderLock implements Runnable{

    private String lockA;
    private String lockB;

    public ThreadHolderLock(String lockA, String lockB){
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + "\t 持有锁 "+ lockA+", 尝试获得"+ lockB);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + "\t 持有锁 "+ lockB+", 尝试获得"+ lockA);
            }
        }
    }
}
  1. 使用jps -l命令找到程序进程;
  2. 使用jstack pid命令打印堆栈信息;
    Found one Java-level deadlock:
    =============================
    "线程BBB":
      waiting to lock monitor 0x00007feb0d80b018 (object 0x000000076af2d588, a java.lang.String),
      which is held by "线程AAA"
    "线程AAA":
      waiting to lock monitor 0x00007feb0d80d8a8 (object 0x000000076af2d5c0, a java.lang.String),
      which is held by "线程BBB"
    
    Java stack information for the threads listed above:
    ===================================================
    "线程BBB":
        at com.github.springcloud.service.ThreadHolderLock.run(MainTest.java:35)
        - waiting to lock <0x000000076af2d588> (a java.lang.String)
        - locked <0x000000076af2d5c0> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
    "线程AAA":
        at com.github.springcloud.service.ThreadHolderLock.run(MainTest.java:35)
        - waiting to lock <0x000000076af2d5c0> (a java.lang.String)
        - locked <0x000000076af2d588> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
    
    Found 1 deadlock.
    

解决方案

使用资源有序分配法避免死锁

想要如何避免死锁,就要弄清楚死锁出现的原因,造成死锁必须达成的4个条件:

  • 互斥条件:一个资源每次只能被一个线程使用。例如,如果线程 A 已经持有的资源,不能再同时被线程 B 持有,如果线程 B 请求获取线程 A 已经占用的资源,那线程 B 只能等待,直到线程 A 释放了资源。
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。例如,当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 C 持有了,所以线程 A 就会处于等待状态,但是线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源 1。
  • 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。例如,当线程A已经持有了资源 ,在自己使用完之前不能被其他线程获取,线程 B 如果也想使用此资源,则只能在线程 A 使用完并释放后才能获取。
  • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。比如,线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环。

避免死锁的产生就只需要破环其中一个条件就可以,最常见的并且可行的就是使用资源有序分配法,来破循环等待条件。资源有序分配法指的是,线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。给资源分配一个全局的唯一编号,进程必须按资源编号的顺序请求资源。这种方法可以避免循环等待,从而防止死锁。

class Resource {
    private final int id;

    public Resource(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }
}

class Process extends Thread {
    private final int id;
    private final Resource[] resources;

    public Process(int id, Resource[] resources) {
        this.id = id;
        this.resources = resources;
    }

    @Override
    public void run() {
        try {
            acquireResources();
            // 模拟处理
            Thread.sleep((int) (Math.random() * 1000));
            releaseResources();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void acquireResources() throws InterruptedException {
        for (Resource resource : resources) {
            synchronized (resource) {
                System.out.println("Process " + id + " acquired Resource " + resource.getId());
            }
        }
    }

    private void releaseResources() {
        for (Resource resource : resources) {
            synchronized (resource) {
                System.out.println("Process " + id + " released Resource " + resource.getId());
            }
        }
    }
}

public class ResourceOrderingExample {
    public static void main(String[] args) {
        Resource resource1 = new Resource(1);
        Resource resource2 = new Resource(2);
        Resource resource3 = new Resource(3);

        Process process1 = new Process(1, new Resource[]{resource1, resource2});
        Process process2 = new Process(2, new Resource[]{resource2, resource3});
        Process process3 = new Process(3, new Resource[]{resource3, resource1});

        process1.start();
        process2.start();
        process3.start();
    }
}
使用银行家算法避免死锁

在这里插入图片描述

银行家算法:一个避免死锁的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法。它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。

在银行中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。通过判断借贷是否安全,然后决定借不借。举例,现有公司B、公司A、公司T,想要从银行分别贷款70亿、40亿、50亿,假设银行只有100亿供放贷,如果借不到企业最大需求的钱,钱将不会归还,怎么才能合理的放贷?

公司最大需求已借走最多还借
B702050
A401030
T503020

此时公司B、A、T已经从银行借走60亿,银行还剩40亿。此时银行可放贷金额组合:

  • 借给公司B10亿、公司A10亿、公司T20亿,等待公司T还钱再将10亿借给公司A,等待公司A还钱,再将钱借给公司B;
  • 借给公司T20亿,等公司T还钱再将钱借给公司A,等待公司A还钱再将钱借给公司B;
  • 借给公司A10亿,等待公司A还钱再将钱借给公司T,公司T还钱再将钱借给公司B;
class Banker {
    private int[] available;  // 系统可用资源
    private int[][] maximum;  // 每个进程的最大资源需求
    private int[][] allocation; // 每个进程当前已分配的资源
    private int[][] need;      // 每个进程剩余的资源需求

    public Banker(int[] available, int[][] maximum) {
        this.available = available;
        this.maximum = maximum;
        int numProcesses = maximum.length;
        int numResources = available.length;
        allocation = new int[numProcesses][numResources];
        need = new int[numProcesses][numResources];
        for (int i = 0; i < numProcesses; i++) {
            for (int j = 0; j < numResources; j++) {
                need[i][j] = maximum[i][j]; // 初始时,Need等于Maximum
            }
        }
    }

    // 请求资源的方法
    public synchronized boolean requestResources(int processId, int[] request) {
        if (!isRequestValid(processId, request)) {
            return false; // 请求不合法,拒绝请求
        }

        // 试探性分配
        for (int i = 0; i < available.length; i++) {
            available[i] -= request[i];
            allocation[processId][i] += request[i];
            need[processId][i] -= request[i];
        }

        // 安全性检查
        boolean safe = isSafeState();

        if (!safe) {
            // 如果不安全,恢复试探性分配前的状态
            for (int i = 0; i < available.length; i++) {
                available[i] += request[i];
                allocation[processId][i] -= request[i];
                need[processId][i] += request[i];
            }
        }

        return safe;
    }

    private boolean isRequestValid(int processId, int[] request) {
        for (int i = 0; i < request.length; i++) {
            if (request[i] > need[processId][i] || request[i] > available[i]) {
                return false; // 请求超出需求或可用资源
            }
        }
        return true;
    }

    private boolean isSafeState() {
        int[] work = available.clone();
        boolean[] finish = new boolean[allocation.length];

        while (true) {
            boolean found = false;
            for (int i = 0; i < allocation.length; i++) {
                if (!finish[i]) {
                    boolean canProceed = true;
                    for (int j = 0; j < work.length; j++) {
                        if (need[i][j] > work[j]) {
                            canProceed = false;
                            break;
                        }
                    }
                    if (canProceed) {
                        for (int j = 0; j < work.length; j++) {
                            work[j] += allocation[i][j];
                        }
                        finish[i] = true;
                        found = true;
                    }
                }
            }
            if (!found) {
                break;
            }
        }

        for (boolean f : finish) {
            if (!f) {
                return false; // 存在未完成的进程,系统不安全
            }
        }
        return true; // 所有进程都完成,系统安全
    }
}

调用示例

public class BankerExample {
    public static void main(String[] args) {
        int[] available = {3, 3, 2};
        int[][] maximum = {
            {7, 5, 3},
            {3, 2, 2},
            {9, 0, 2},
            {2, 2, 2},
            {4, 3, 3}
        };

        Banker banker = new Banker(available, maximum);

        int[] request1 = {1, 0, 2};
        boolean granted1 = banker.requestResources(1, request1);
        System.out.println("Request 1 granted: " + granted1);

        int[] request2 = {3, 3, 0};
        boolean granted2 = banker.requestResources(4, request2);
        System.out.println("Request 2 granted: " + granted2);

        int[] request3 = {2, 0, 0};
        boolean granted3 = banker.requestResources(0, request3);
        System.out.println("Request 3 granted: " + granted3);
    }
}
使用tryLock进行超时锁定

使用java.util.concurrent.locks.ReentrantLocktryLock方法可以尝试获取锁,并设置超时时间,避免长时间等待造成的死锁。

class Process extends Thread {
    private final int id;
    private final Lock lock1;
    private final Lock lock2;

    public Process(int id, Lock lock1, Lock lock2) {
        this.id = id;
        this.lock1 = lock1;
        this.lock2 = lock2;
    }

    @Override
    public void run() {
        try {
            while (true) {
                if (lock1.tryLock(50, TimeUnit.MILLISECONDS)) {
                    try {
                        if (lock2.tryLock(50, TimeUnit.MILLISECONDS)) {
                            try {
                                System.out.println("Process " + id + " acquired both locks");
                                // 模拟处理
                                Thread.sleep((int) (Math.random() * 1000));
                                return;
                            } finally {
                                lock2.unlock();
                            }
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
                // 等待一段时间再重试
                Thread.sleep((int) (Math.random() * 50));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class TryLockExample {
    public static void main(String[] args) {
        Lock lock1 = new ReentrantLock();
        Lock lock2 = new ReentrantLock();

        Process process1 = new Process(1, lock1, lock2);
        Process process2 = new Process(2, lock2, lock1);

        process1.start();
        process2.start();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_whitepure

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值