当前位置: 首页 > 知识库问答 >
问题:

Java同步:在账户对之间原子移动资金?

宋鸿云
2023-03-14

我怎样才能把钱从一个账户转到另一个账户?上课时间:

public class Account {
    public Account(BigDecimal initialAmount) {...}
    public BigDecimal getAmount() {...}
    public void setAmount(BigDecimal amount) {...}
}

我希望得到以下伪代码:

public boolean transfer(Account from, Account to, BigDecimal amount) {
    BigDecimal fromValue = from.getAmount();
    if (amount.compareTo(fromValue) < 0)
         return false;
    BigDecimal toValue = to.getAmount();
    from.setAmount(fromValue.add(amount.negate()));
    to.setAmount(toValue.add(amount));
    return true;
}

在单线程(或顺序)环境中安全地更新帐户。

在多线程/并发环境中,我看到了危险情况:

acc1 --> acc2  ||  acc2 --> acc1
acc1 --> acc2  ||  acc2 --> acc3  ||  acc3 --> acc1
...

最简单的解决方案是阻止共享对象,但对于以下情况,这将是低效的:

acc1 --> acc2  ||  acc3 --> acc4  and  acc1 != acc3 and acc2 != acc4

我希望独立的传输是并行进行的。

更新似乎建议的解决方案:

synchronize (acc1) {
   synchronize (acc2) {
     ....
   }
}

导致死锁,因为两个锁被依次获取。。。

更新2“在多线程环境中安全更新帐户”到底是什么意思?唯一担心的是这些账户最终不会出现负资金,还是存在其他问题?

如果acc1(2);acc2(3)acc1--1--

您希望一次有多少并发事务?1000-10000——所以锁定共享对象是没有效率的。


共有3个答案

丌官淇
2023-03-14

我建议创建一个方法帐户。提取(金额),如果没有足够的资金,就会抛出异常。此方法需要在帐户本身上同步。

编辑:

还需要一个账户。在接收帐户实例上同步的存款(金额)方法。

基本上,这将导致在取款时锁定第一个账户,然后在存款时锁定另一个收款账户。所以有两把锁,但不能同时锁。

代码示例:假设提取/存款是同步的,并返回布尔成功状态,而不是抛出异常。

public boolean transfer(Account from, Account to, BigDecimal amount) {
    boolean success = false;
    boolean withdrawn = false;
    try {
        if (from.withdraw(amount)) {
            withdrawn = true;
            if (to.deposit(amount)) {
                success = true;
            }
        }
    } finally {
        if (withdrawn && !success) {
            from.deposit(amount);
        }
    }

    return success;
}
齐起运
2023-03-14

一种方法是有一个交易日志。在转移资金之前,你需要在每个账户的交易日志中写入你打算做的事情。日志应该包含:从账户中取出的金额,以及日志对之间共享的锁。

最初,锁应该处于阻塞状态。您创建了日志对,一个带有金额为X,另一个带有金额为-X,并且两者共享一个锁。然后将日志条目发送到各自账户的收件箱,从其中取出资金的账户应该保留该金额。一旦您确认它们是安全交付的,然后释放锁。锁被释放的那一刻,您就处于无法返回的时刻。然后账户应该自行解决。

如果任何一方希望在释放锁之前的任何时候交易失败,那么只需删除日志并将预留金额返回主余额。

这种方法可能有点繁重,但它也适用于分布式场景,在这种场景中,帐户实际上位于不同的机器中,并且收件箱实际上必须保持不变,以确保在任何机器意外崩溃/脱机时,钱永远不会丢失。其一般技术称为两相锁定。

杨凯旋
2023-03-14

一个简单的解决方案可以是每个帐户使用一个锁,但是为了避免死锁,您必须总是以相同的顺序获取锁。因此,您可以有一个最终的帐户ID,并首先获取ID较小的帐户的锁:

public void transfer(Account acc1, Account acc2, BigDecimal value) {
    Object lock1 = acc1.ID < acc2.ID ? acc1.LOCK : acc2.LOCK;
    Object lock2 = acc1.ID < acc2.ID ? acc2.LOCK : acc1.LOCK;
    synchronized (lock1) {
       synchronized (lock2) {
          acc1.widrawal(value);
          acc2.send(value);
       }
    }
}
 类似资料:
  • 目前我正在尝试通过AzureDevops创建动态环境。 实现这一点的其中一个步骤是获取生产数据库的副本并将其放置在临时资源组(生产订阅)中,然后将sql server和关联数据库移动到非生产订阅中。然后,我们从这里创建web应用程序并部署代码。 当我通过Az Cli运行此命令时,我可以使用以下命令移动资源 然而,当我通过AzureDevops运行此程序时,我得到以下错误 我相信,在AzureDev

  • 问题内容: 这个问题一再被问到,但我仍然有疑问。当人们说同步创建了一个内存障碍时,这个内存障碍适用于什么缓存变量?这看起来不可行。 因此,由于这个疑问,我编写了一些看起来像这样的代码: 我想知道是否有可能只用简单的double []代替total的类型:这将要求synced(总计)(在run()方法中)确保我不使用索引中的每个索引的本地副本双精度数组,即内存围栏不仅适用于自身的值(在指针的背后),

  • 1.2 商户资金账户管理 1.2.1商户充值 【场景介绍】 商户将资金充值到账户余额。 【重要说明】如使用钱麦收银台,当前此URL仅支持在PC端打开。如在移动端打开,则会报错。 【请求地址】 环境 接口服务URI 生产环境 /rest/v1.0/order/merchantRecharge 1.2.1.1请求参数: 参数名称 参数含义 数据类型 必填 参数说明 requestNo 商户请求号 St

  • 我是一名编码等方面的初学者,但我正在努力理解我在做什么以及代码中发生了什么。 我想在不同的电子表格之间划一行。 我在“核心”电子表格中有一个脚本,它在“主页”和“完成”表格链接之间移动一行到电子表格: 我搜索了如何将位于“已完成”工作表上的数据移动到另一个名为“数据库”的电子表格链接到电子表格: 但我想将数据从表“CORE”/“Finished”传输到表“database”/“DB”,而不重写DB

  • 下图应该有助于尽可能清楚地说明这一点。 我有三个窗格A、B和C。B和C是A的子窗格,B有自己的子窗格(白色框)。通过拖放,我可以将B的孩子移动到B中的任何位置,并能够再次拾取他们,但如果拖到C,甚至是A的一小部分,他们可以被丢弃,但不能再次拾取。 我使用基本控制。要拖动,我使用imgView.setOnMouseDragged。 那么,我如何才能从一个窗格A拖动到窗格B或C,并且仍然允许拾取和拖动