- 1. 业务场景
- 2. 问题分析
- 3. 注意锁的对象
简单模拟一个A账户像B账户转账的场景,A账户减少1元,B账户就增加1元。
代码实现如下,为了演示多线程共享资源竞争的问题,使用CountDownLatch模拟并发。
public class Test_01 {
public static final CountDownLatch start = new CountDownLatch(1);
public static final CountDownLatch done = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
Account a = new Account();
a.setBalance(new BigDecimal("100"));
Account b = new Account();
b.setBalance(new BigDecimal("0"));
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
start.await();
a.transfer(b, new BigDecimal("1"));
done.countDown();
}
}).start();
}
start.countDown();
done.await();
System.out.println("A账户余额:" + a.getBalance());
System.out.println("B账户余额:" + b.getBalance());
}
}
@Data
class Account {
private BigDecimal balance;
public void transfer(Account target, BigDecimal amount) {
if (this.balance.compareTo(amount) >= 0) {
this.balance = balance.subtract(amount);
target.balance = target.balance.add(amount);
} else {
System.out.println("余额不足!");
}
}
}
执行可能出现如下结果。
问题很明显,肯定是transfer
方法中对balance
这个共享资源的访问出现了问题,此时,最容易想到的解决方式就是在transfer
上加上synchronized
关键字。
public synchronized void transfer(Account target, BigDecimal amount) {
if (this.balance.compareTo(amount) >= 0) {
this.balance = balance.subtract(amount);
target.balance = target.balance.add(amount);
} else {
System.out.println("余额不足!");
}
}
的确,加上synchronized
可以解决当前的问题,但如果现在是3个账户转钱呢?
比如像下面这样,a
给b
转钱,然后b
又把钱转给了c
public class Test_01 {
public static final CountDownLatch start = new CountDownLatch(1);
public static final CountDownLatch done = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
Account a = new Account();
a.setBalance(new BigDecimal("100"));
Account b = new Account();
b.setBalance(new BigDecimal("0"));
Account c = new Account();
c.setBalance(new BigDecimal("0"));
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
start.await();
a.transfer(b, new BigDecimal("1"));
b.transfer(c, new BigDecimal("1"));
done.countDown();
}
}).start();
}
start.countDown();
done.await();
System.out.println("A账户余额:" + a.getBalance());
System.out.println("B账户余额:" + b.getBalance());
System.out.println("C账户余额:" + c.getBalance());
}
}
@Data
class Account {
private BigDecimal balance;
public synchronized void transfer(Account target, BigDecimal amount) {
if (this.balance.compareTo(amount) >= 0) {
this.balance = balance.subtract(amount);
target.balance = target.balance.add(amount);
} else {
System.out.println("余额不足!");
}
}
}
结果又错了
为什么加了synchronized
还会有问题呢?原因就在于锁的对象的问题,synchronized
关键字加在非静态的方法上时,锁的是当前的实例对象,也就是this
这个对象,而我们的transfer
中实际上还包含了一个target
对象,而这个对象中的balance
则是没有受到保护的,因此出现了线程安全的问题。
问题找到了,解决方式就很容易,只需要找一个共享的对象作为锁即可。
public class Test_01 {
public static final CountDownLatch start = new CountDownLatch(1);
public static final CountDownLatch done = new CountDownLatch(10);
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Account a = new Account();
a.setBalance(new BigDecimal("100"));
Account b = new Account();
b.setBalance(new BigDecimal("0"));
Account c = new Account();
c.setBalance(new BigDecimal("0"));
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
start.await();
a.transfer(b, new BigDecimal("1"), lock);
b.transfer(c, new BigDecimal("1"), lock);
done.countDown();
}
}).start();
}
start.countDown();
done.await();
System.out.println("A账户余额:" + a.getBalance());
System.out.println("B账户余额:" + b.getBalance());
System.out.println("C账户余额:" + c.getBalance());
}
}
@Data
class Account {
private BigDecimal balance;
public void transfer(Account target, BigDecimal amount, Object lock) {
synchronized (lock){
if (this.balance.compareTo(amount) >= 0) {
this.balance = balance.subtract(amount);
target.balance = target.balance.add(amount);
} else {
System.out.println("余额不足!");
}
}
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)