V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
coderstory
V2EX  ›  Java

Java 新手 求解 死锁要怎么处理

  •  
  •   coderstory ·
    coderstory · 2021-07-21 19:05:38 +08:00 · 2264 次点击
    这是一个创建于 981 天前的主题,其中的信息可能已经有所发展或是发生改变。

    题目是 10 个线程 对应 20 个账号 每个线程执行 100 次 任意 2 个账号之间的金额交易

    代码实现中遇到了死锁

    假设线程 A 对 账户 A 账户 B 执行交易 ( A 转钱到 B ) 线程 B 对账户 B 和账户 A 执行交易( B 转钱到 A )

    我代码写的是先锁一个账号 判断账号余额是否足够,然后锁另一个账户 最后执行转账

    但是同时有 2 个线程执行了 2 个相同账户的反向操作时,就会死锁了。

    package cn.bobmao.logic.service;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class Test {
        class Account {
            int index;
            int balance = 100;
    
            public Account(int index) {
                this.index = index;
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new Test().run();
        }
    
        public void run() throws InterruptedException {
            AtomicInteger sum = new AtomicInteger(0);
            List<Account> accounts = new ArrayList<>();
            for (int i = 0; i < 20; i++) {
                accounts.add(new Account(i));
            }
            ExecutorService executorService = Executors.newFixedThreadPool(10);
    
            CountDownLatch latch = new CountDownLatch(1000);
            for (int i = 0; i < 10; i++) {
                executorService.submit(new Pay(accounts, latch, sum));
            }
    
            latch.await();
    
    
            int count = 0;
            for (Account account : accounts) {
                count += account.balance;
            }
    
            System.out.println(count);
    
    
        }
    
        class Pay implements Runnable {
            List<Account> accounts;
            CountDownLatch latch;
            AtomicInteger sum;
    
            public Pay(List<Account> accounts, CountDownLatch latch, AtomicInteger sum) {
                this.accounts = accounts;
                this.latch = latch;
                this.sum = sum;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    int money = new Random().nextInt(100);
                    // a->b
                    int a = new Random().nextInt(20);
                    int b = new Random().nextInt(20);
                    latch.countDown();
                    if (a == b) {
                        System.out.println("当前执行次数" + sum.getAndIncrement());
    
                        continue;
                    }
                    Account ac = accounts.get(a);
                    synchronized (ac) {
                        System.out.println("锁 ac " + ac.index + " " + Thread.currentThread().getName());
                        if (ac.balance >= money) {
                            Account bc = accounts.get(b);
                            synchronized (bc) {
                                System.out.println("锁 bc " + ac.index + " " + Thread.currentThread().getName());
                                ac.balance = ac.balance - money;
                                bc.balance = bc.balance + money;
                                //latch.countDown();
                            }
                            System.out.println("解锁 bc " + ac.index + " " + Thread.currentThread().getName());
                        }
                    }
                    System.out.println("解锁 ac " + ac.index + " " + Thread.currentThread().getName());
                    System.out.println("当前执行次数" + sum.getAndIncrement());
                }
            }
        }
    }
    
    
    
    9 条回复    2021-07-22 22:09:57 +08:00
    kekxv
        1
    kekxv  
       2021-07-21 19:17:28 +08:00 via iPhone   ❤️ 1
    先锁支出的,拿走,解锁,再锁接收的
    lakehylia
        2
    lakehylia  
       2021-07-21 19:28:15 +08:00
    扣完钱就可以解锁转出账户了,然后再尝试锁转入账户加钱。
    zhgg0
        3
    zhgg0  
       2021-07-21 20:07:07 +08:00   ❤️ 1
    所有线程按同一个顺序加锁。比如你这个情况,可以按照账号 id 的顺序加锁。
    不管是账号 A 转给账号 B,还是账号 B 转给账号 A ;两个线程都是先锁账号 A,再锁账号 B 。
    yogogo
        4
    yogogo  
       2021-07-22 07:53:32 +08:00
    转账交易,AB 两个账号都应该同时加锁
    lff0305
        5
    lff0305  
       2021-07-22 08:09:48 +08:00 via Android
    帐号 a,b 排个序,加锁小的,再加锁大的,try 转账,finally 解锁大的,解锁小的 肯定没死锁
    no1xsyzy
        6
    no1xsyzy  
       2021-07-22 09:06:56 +08:00
    哲学家就餐问题
    wqhui
        7
    wqhui  
       2021-07-22 09:41:11 +08:00
    两个账号一起锁了
    yuk1no
        8
    yuk1no  
       2021-07-22 10:21:27 +08:00 via iPhone
    直接 stm
    MoHen9
        9
    MoHen9  
       2021-07-22 22:09:57 +08:00 via Android
    不管是 A 给 B 转账,还是 B 给 A 转账,逻辑都是一样的。
    扣钱时锁被扣钱的账户,加钱时锁被加钱的账户。

    两个独立账户没必要一起上锁,只要遇到错误可回退就可以,而且各自的账户可以用各自的锁,也不用争同一个锁。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   972 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 20:46 · PVG 04:46 · LAX 13:46 · JFK 16:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.