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