Java多线程 死锁转账案例

两个转账的死锁demo

需要两把锁: 转账时, 锁住自己的账户, 同时只能有一个线程去执行转账.
获取两把锁成功, 且余额大于0 , 则扣除转出人的钱, 增加收款人的钱. 并且是原子操作.

出现死锁的情况: 对方给我转钱 ,我也给对方转钱, 那么双方都持有自己的锁, 并且需要对方的锁, 这就造成了死锁.

如下的代码演示了转账死锁的发生.
在run方法中, 根据不同线程的flag 执行不同的转账方法,分别给a和b相互的转账.
transferMoney方法就是转账的方法.
在转账的方法中, 分别要获取转账方和收款方的两把锁, 才能进行转账的操作.

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);

    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) {
    
    
        synchronized (from) {
    
    
            synchronized (to) {
    
    
                //转账前判断余额是否充足
                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());
            }
        }

    }

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

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

}

运行程序, 此时成功的执行的转账的操作. 线程名为0和1 的线程, 分别去转账的方法中执行的转账,由于相互给对方转账200. 因此余额不变.

修改如下:

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);

    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) {
    
    
        synchronized (from) {
    
    
            try {
    
    
                Thread.sleep(20);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            synchronized (to) {
    
    
                //转账前判断余额是否充足
                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());
            }
        }

    }

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

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

修改了获取第二把锁之前, 给当前线程休眠20ms .

此时再运行程序, 可以看到控制台什么都没有打印, 红色的按钮一直亮着, 说明进入了死锁的状态.
两个线程都只是执行到上图中的第67行代码就执行不下去了.

死锁的原因分析. 第一个线程进入如下的方法后, 获得了from这把锁, 接着sleep,进入timed_waiting状态, 并且由于是sleep, 并不会释放from这把锁.
此时CPU调度执行第二个线程, 第二个线程的from为第一个线程的to锁, 第二个线程获得了to锁后, 也进入timed_waiting状态, 持有to锁不会释放.
接着CPU切回第一个线程,想要去获取to锁, 但是获取不到,因为被第二个线程所持有着.
第二个线程持有to锁, 想要去获得from锁也获取不到, 那么就进入了死锁的状态.

死锁形成的原因就是如下的方法中, 锁获得的循序是相反的,

补充: 上面demo代码给两个账户类, 创建的都是static对象, 而static对象在整个Java虚拟机中只有一个实例, 所以不同线程获取锁的时候, 始终只有一个线程获得到这把锁. 此种情况适用于单体的项目. 而分布式的情况获得锁是不一样的,需要另外的方法进行处理.

猜你喜欢

转载自blog.csdn.net/qq_33229669/article/details/108475207