今天在网上看到一段代码,是模拟银行转账的,如何保证多次转账并发执行的时候,转出账户和转入账户的金额一致。
代码可谓巧妙绝伦!先看代码:
public class MyLock { public static void main(String[] args) { // 模拟转出和转入账户 Account src = new Account(100000); Account target = new Account(100000); // 设置倒计时 CountDownLatch countDownLatch = new CountDownLatch(99999); for (int i = 0; i < 99999; i++) { Thread t1 = new Thread(() -> { // 每次转1元钱,共转9999次 src.transactionToTarget(1, target); countDownLatch.countDown(); }); t1.setName("Thread: " + i); t1.start(); } try { // 等待所有线程执行完毕 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } // 打印账户余额 System.out.println("src: " + src.getBanalce()); System.out.println("target: " + target.getBanalce()); } /** * 账户类 */ static class Account { // 余额 private Integer banalce; public Account(Integer banalce) { this.banalce = banalce; } /** * 转账 */ public void transactionToTarget(Integer money, Account target) { Allocator.getInstance().apply(this, target); this.banalce = this.banalce - money; target.setBanalce(target.getBanalce() + money); Allocator.getInstance().release(this, target); } public Integer getBanalce() { return banalce; } public void setBanalce(Integer banalce) { this.banalce = banalce; } } /** * 账户管理器 */ static class Allocator { private Allocator() { } // 账户锁 private List<Account> locks = new ArrayList<>(); /** * 为转出账户和转入账户申请锁。 * * @param src * @param target */ public synchronized void apply(Account src, Account target) { while (locks.contains(src) || locks.contains(target)) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } locks.add(src); locks.add(target); } /** * 释放锁。 * * @param src * @param target */ public synchronized void release(Account src, Account target) { locks.remove(src); locks.remove(target); this.notifyAll(); } public static Allocator getInstance() { return AllocatorSingle.install; } static class AllocatorSingle { public static Allocator install = new Allocator(); } } }
有几个关键点:
1、为了在转账前,同时获得转出账户和转入账户的锁,设计了Allocator这个账户的管理器。
它就像一个账户管理员,拿着所有账户的账本。当有申请者来申请账户锁的时候,他会控制转入账户和转出账户同时存在;如果有其他线程当前正在操作这两个账户(正在执行账户的转入或转出),则当前线程就开始wait;否则,就让当前线程获得这两个账户的锁。
2、上面的代码里,wait放在了while里,而不能改成if!
原因:
模拟执行:假设有A,B,C三个线程,A线程先执行apply()方法,while检查为false,成功获得锁后开始执行。
如果此处的while改成if:因为C执行完apply()方法后,释放了apply()方法的操作权,
)
所以,几乎所有的java书籍都会建议开发者永远都要把wait()放到循环语句里面。
这是前车之鉴,不是没有道理的。
文章来源: https://blog.csdn.net/qq_26898645/article/details/89064594