美文网首页
死锁问题

死锁问题

作者: From64KB | 来源:发表于2020-11-17 09:55 被阅读0次

记得以前看过一个笑话:面试官问死锁怎么回事儿?我想了想,然后回答面试官说你先发offer给我,我再回答你。这就是现实中的死锁。
代码中的死锁是当两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,就称为死锁。举个简单的例子,线程1先持有了锁A,准备再请求锁B,但是在这之前线程2持有了锁B准备请求锁A,这时双方就会造成死锁。

上面这个简化过的例子只能用于理解死锁的形成,对于解决死锁而言参考价值有限。现实的代码中,锁的方式多种多样有方法上的synchronizedReentrantLock等等,当他们混在一起用的时候,解决起来就比较棘手了。比如下面一些例子:

    public void testLock() throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        reentrantLock.unlock();

        ArrayBlockingQueue<Integer> integers = new ArrayBlockingQueue<>(16);
        integers.take();

        Semaphore semaphore = new Semaphore(1);
        semaphore.acquire();
        semaphore.release();
    }

    public synchronized void process() { //lock on `this`

    }

    public static synchronized void processStatic() { //lock on class

    }

很多时候要直接从代码逻辑上找到死锁是很困难的。必须要实际遇到死锁并借助一些工具才能解决这个问题。

  1. 线程dump
    使用jstack <process id>或者kill -3 <process id>
    image.png
    可以看到Found one Java-level deadlock:和下面输出的详细信息。通过这些信息可以辅助解决死锁问题。
  2. 通过Java SDK提供的JVM死锁检查api:
    public void detectDeadlock() {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        boolean findDeadlock = deadlockedThreads != null && deadlockedThreads.length > 0;
        System.out.println("Find deadlock:" + findDeadlock);
    }

当然为了能及时检查出死锁,需要开启一个后台线程并进行合适频率的轮询。

上面的方法是在出现死锁时的辅助检查手段,有没有什么方法可以尽量在代码中避免出现死锁呢?有,获取锁的时候增加超时限制。

    public void lockWithTimeout() throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        boolean acquire = reentrantLock.tryLock(2, TimeUnit.MINUTES);

        ArrayBlockingQueue<Integer> integers = new ArrayBlockingQueue<>(16);
        integers.poll(2, TimeUnit.MINUTES);

        Semaphore semaphore = new Semaphore(1);
        semaphore.tryAcquire(2, TimeUnit.MINUTES);
    }

OK,让我们再来看一个例子,下面的代码是否会形成死锁呢?

    public void doTransfer() {
        Account account1 = new Account();
        Account account2 = new Account();
        //线程1
        new Thread(() -> transfer(account1, account2, 100)).start();
        //线程2
        new Thread(() -> transfer(account2, account1, 200)).start();//注意和上面的传参顺序相反
    }

    /**
     * 从account2转账至account1
     *
     * @param account1
     * @param account2
     * @param amount
     */
    public void transfer(Account account1, Account account2, int amount) {
        synchronized (account1) {
            synchronized (account2) {
                account1.add(amount);
                account2.reduce(amount);
            }
        }
    }

    class Account {

        public void add(int amount) {

        }

        public void reduce(int amount) {

        }
    }

这样是会形成死锁的。线程1如果刚走到synchronized (account1)这里,而线程2也走到synchronized (account1)这里,由于account1的实际传入对象分别为account1account2,这时再向下走就会形成死锁。那怎么改上面的代码就可以避免死锁呢?下面是修改过的代码:

    public void doTransfer() {
        Account account1 = new Account();
        Account account2 = new Account();
        new Thread(() -> transfer(account1, account2, 100)).start();
        new Thread(() -> transfer(account2, account1, 200)).start();
    }

    /**
     * 从account2转账至account1
     *
     * @param account1
     * @param account2
     * @param amount
     */
    public void transfer(Account account1, Account account2, int amount) {
        //对account1和account2排序,使无论account1和account2如何传入在加锁时顺序一致
        Account firstAccount = getFirstAccount(account1, account2);
        Account secondAccount = getSecondAccount(account1, account2);
        synchronized (firstAccount) {
            synchronized (secondAccount) {
                account1.add(amount);
                account2.reduce(amount);
            }
        }
    }

    private Account getFirstAccount(Account account1, Account account2) {
        boolean b = account1.getId() > account2.getId();//仅作示例,表示account1某种固定属性可以和account2比较
        if (b) {
            return account1;
        } else {
            return account2;
        }
    }

    private Account getSecondAccount(Account account1, Account account2) {
        boolean b = account1.getId() < account2.getId();//仅作示例,表示account1某种固定属性可以和account2比较
        if (b) {
            return account1;
        } else {
            return account2;
        }
    }

    class Account {

        public void add(int amount) {

        }

        public void reduce(int amount) {

        }

        public int getId() {
            return 1;
        }
    }

通过保持传入对象加锁顺序一致就可避免死锁问题。

相关文章

  • jstack命令:教你如何排查多线程问题

    这是之前的一个死锁案例: 一个多线程死锁案例,如何避免及解决死锁问题? 如程序中发生这样的死锁问题该如何排查呢?我...

  • 死锁问题

    产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请...

  • 死锁问题

    判断是否死锁 终极方法是判断死锁问题产生的根源是dispatch_sync 是否是发生在当前dispatch_s...

  • 死锁问题

    记得以前看过一个笑话:面试官问死锁怎么回事儿?我想了想,然后回答面试官说你先发offer给我,我再回答你。这就是现...

  • 01-iOS多线程 ——图解死锁

    死锁 - 死锁问题,是iOS开发中必须要理解的问题,可能光看一些讲解死锁的代码并不是很容易理解。我制作了下面这幅图...

  • 分布式数据库系列-死锁处理

    死锁检测 假如我们允许发生死锁并且依赖于死锁检测,那么分布式系统中的主要问题就是如何维护等待图,。处理这个问题的常...

  • 并发insert on duplicate key update

    前言 数据库死锁问题,是一个老生常谈且很常见的问题,网上也有非常多对于各类死锁场景的解析和复现,但凡和死锁有关,无...

  • 记spring事务传播机制引发连接池死锁问题及解决方案

    公司项目开发时,遇到了一个死锁问题,项目会时不时的死掉,于是开始了对死锁问题的排查。 偶然间,项目再一次出现了死锁...

  • 漫谈死锁

    一、前言 每个MySQL DBA和开发大概率都会遇到死锁问题,本文是自己对死锁相关知识总结,介绍死锁是什么,MyS...

  • 死锁问题和并发问题

    如果没有锁就不存在死锁问题。 因此死锁问题是并发问题的衍生问题,因为要解决并发问题,就设计了锁这个解决方案,因为要...

网友评论

      本文标题:死锁问题

      本文链接:https://www.haomeiwen.com/subject/nrmzbktx.html