Java多线程 修复死锁的三个策略&& 避免策略代码演示

文章目录

      • 死锁的特点与修复策略
      • 避免策略代码演示

死锁的特点与修复策略

死锁的特点:

  1. 不可提前预料
  2. 蔓延速度快
  3. 危害非常大

发生死锁后, 要保存案发现场, 可以用java相关的命令, 把整个堆栈信息保存下来, 立刻修复线上服务.

利用保存的堆栈信息, 排查死锁, 修改代码. 重新发版.

修复死锁的三个策略 :

  1. 避免策略 : 哲学家就餐的换手方案, 转账换序方案
  2. 检测与恢复策略: 一段时间检测是否有死锁, 如果有就剥夺某一个资源, 来打开死锁
  3. 鸵鸟策略: 比喻鸵鸟遇到危险把头埋地上而看不到危险, 逃避心理. 含义是如果发生死锁的概率极其的低 , 那么就直接忽略它 , 直到发生死锁的时候再修复. (适用于用户量不大的系统,此种做法不推荐)

避免策略代码演示

死锁避免策略
避免相反的获取锁的顺序:

转账时避免死锁的案例:
不在乎获取锁的顺序. 而是避免相反的获取锁的顺序.

package com.thread.deadlock;

/**
 * 类名称:TransferMoney
 * 类描述: 转账时遇到死锁的代码演示
 *
 * @author: https://javaweixin6.blog.csdn.net/
 * 创建时间:2020/9/8 19:24
 * Version 1.0
 */
public class TransferMoney implements Runnable {
     

    //根据不同的flag, 给不同的人转账
    int flag = 1;

    static Account a = new Account(500);
    static Account b = new Account(500);
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
     
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 0;

        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);

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

        //主线程等待子线程执行完毕
        thread1.join();
        thread2.join();

        System.out.println("a的余额 " + a.balance);
        System.out.println("b的余额 " + b.balance);
    }

    @Override
    public void run() {
     
        //flag 是1 则 a 给b钱
        if (flag == 1) {
     
            transferMoney(a, b, 200);
        }
        //flag 是0  则b 给a钱
        if (flag == 0) {
     
            transferMoney(b, a, 200);
        }
    }

    /**
     * 转账的方法
     *
     * @param from   转账方
     * @param to     收账方
     * @param amount 金额
     */
    public static void transferMoney(Account from, Account to, int amount) {
     
        class Helper {
     

            public void transfer() {
     
                //转账前判断余额是否充足
                if (from.balance - amount < 0) {
     
                    System.out.println("余额不足, 转账失败" + Thread.currentThread().getName());
                    return;
                }

                //余额充足,则进行转账操作. 转账方扣钱,  收款方收钱
                from.balance -= amount;
                to.balance += amount;
                System.out.println("成功转账" + amount + "元 " + Thread.currentThread().getName());

            }

        }

        //获取转出和转入对象的hash值   如果对象不变, hash值不会改变
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);

        if (fromHash < toHash) {
     
            //如果我比你小, 始终是从小往大获取.
            synchronized (from) {
     
                synchronized (to) {
     

                    new Helper().transfer();

                }
            }
        } else if (fromHash > toHash) {
     
            //如果我比你小, 始终是从小往大获取.
            synchronized (to) {
     
                synchronized (from) {
     

                    new Helper().transfer();

                }
            }
        } else {
     
            //此时发生了hash冲突 ,那么用第三把锁来避免hash冲突

            synchronized (lock) {
     
                synchronized (to) {
     
                    synchronized (from) {
     

                        new Helper().transfer();

                    }
                }
            }
        }
    }

    //账户的静态内部类
    static class Account {
     
        //余额
        int balance;

        public Account(int balance) {
     
            this.balance = balance;
        }
    }
}

主要的代码如下 , 算出转出方和转入方对象的hash值, 根据hash值的大小, 来决定获取锁的顺序,来避免相反的获取锁的顺序.
由于from 和to对象定义的是静态的, 因此对象算出来的hash值是不会变的.
始终保证了不会持有对方的锁资源.

对于两个对象算出来的hash值如果一致, 即发生hash冲突时 , 是加上第三把锁, 来让两个线程随机的竞争, 只有其中一个线程拿到了最外层的锁, 并且执行完了代码后, 才会释放最外层的锁.

在实际开发中, 如果主键是自增的, 那么可以用主键计算hash值来避免hash冲突

 int fromHash = System.identityHashCode(from);
 int toHash = System.identityHashCode(to);

        if (fromHash < toHash) {
     
            //如果我比你小, 始终是从小往大获取.
            synchronized (from) {
     
                synchronized (to) {
     

                    new Helper().transfer();

                }
            }
        } else if (fromHash > toHash) {
     
            //如果我比你小, 始终是从小往大获取.
            synchronized (to) {
     
                synchronized (from) {
     
                    new Helper().transfer();
                }
            }
        } else {
     
            //此时发生了hash冲突 ,那么用第三把锁来避免hash冲突

            synchronized (lock) {
     
                synchronized (to) {
     
                    synchronized (from) {
     
                        new Helper().transfer();

                    }
                }
            }
        }

多次运行程序后如下. 没有发生死锁.
Java多线程 修复死锁的三个策略&& 避免策略代码演示_第1张图片

你可能感兴趣的:(Java多线程基础与核心)