
题是 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()); } } } } 1 kekxv 2021 年 7 月 21 日 via iPhone 先锁支出的,拿走,解锁,再锁接收的 |
2 lakehylia 2021 年 7 月 21 日 扣完钱就可以解锁转出账户了,然后再尝试锁转入账户加钱。 |
3 zhgg0 2021 年 7 月 21 日 所有线程按同一个顺序加锁。比如你这个情况,可以按照账号 id 的顺序加锁。 不管是账号 A 转给账号 B,还是账号 B 转给账号 A ;两个线程都是先锁账号 A,再锁账号 B 。 |
4 yogogo 2021 年 7 月 22 日 转账交易,AB 两个账号都应该同时加锁 |
5 lff0305 2021 年 7 月 22 日 via Android 帐号 a,b 排个序,加锁小的,再加锁大的,try 转账,finally 解锁大的,解锁小的 肯定没死锁 |
6 no1xsyzy 2021 年 7 月 22 日 哲学家就餐问题 |
7 wqhui 2021 年 7 月 22 日 两个账号一起锁了 |
8 yuk1no 2021 年 7 月 22 日 via iPhone 直接 stm |
9 MoHen9 2021 年 7 月 22 日 via Android 不管是 A 给 B 转账,还是 B 给 A 转账,逻辑都是一样的。 扣钱时锁被扣钱的账户,加钱时锁被加钱的账户。 两个独立账户没必要一起上锁,只要遇到错误可回退就可以,而且各自的账户可以用各自的锁,也不用争同一个锁。 |