【问题标题】:Preserving lock order in nested synchronized blocks在嵌套同步块中保留锁定顺序
【发布时间】:2015-02-05 17:04:53
【问题描述】:

我有一个类似于this question 中的示例。

在我确保保留获得锁的顺序之后

public class Account {
    private int balance;

    public void withdraw(int value) {
        balance -= value;
    }

    public void deposit(int value) {
        balance += value;
    }
}

public class Bank {
    private List<Account> accounts;
    private int slots;

    public Bank(int slots) {
        accounts = new ArrayList<Account>(Collections.nCopies(slots, new Account()));
        this.slots = slots;
    }

    public void transfer(int fromId, int toId, int value) {
        synchronized(accounts.get(Math.min(fromId, toId))) {
            synchronized(accounts.get(Math.max(fromId, toId))) {
                accounts.get(fromId).withdraw(value);
                accounts.get(toId).deposit(value);
        }
    }
}

我的理解是,如果一个线程执行从A到B的转账,而另一个线程想要从B到A的转账,那么它必须等待锁(如果我错了,请纠正我)。

确保只有一个线程可以从 A 传输到 B,而另一个线程可以从 B 传输到 A 的最佳方法是什么?

我正在考虑为每个 Account 对象声明两组锁:

public class Account{
    public Object fromLock = new Object();
    public Object toLock = new Object();
    // ...
}

并获得其中一个,具体取决于转账是来自给定账户还是来自给定账户。这是正确的做法吗?

编辑:问题是:假设提款和存款方法需要一些时间才能完成。您如何允许 2 个用户(与不同的线程相关联)在不等待的情况下同时相互传输?

【问题讨论】:

  • 快速评论,你设置的构造函数mBalance = 0在java中是没有必要的。您可以阅读docs.oracle.com/javase/tutorial/java/nutsandbolts/… 了解更多信息。此外,用“m”en.wikipedia.org/wiki/Naming_convention_(programming)#Java 命名变量并不常见
  • 这没有多大意义:如果一个线程退出而另一个线程存款,您的帐户将不再是线程安全的。状态 mBalance 必须由单个锁保护。
  • @JBNizet,为什么没有意义?不然你怎么让我给你转钱,而你同时给我转钱?
  • 如果你允许,那么一个线程会尝试减少 mBalance,而另一个线程会尝试增加它。这两个操作必须是互斥的,否则你可能会得到一个错误的 mBalance。这正是您需要同步的原因。
  • @Emz,我最初重用了链接问题中的代码。现在修好了。

标签: java multithreading locking synchronized


【解决方案1】:

按照您实施帐户的方式,您永远不会希望多个交易在任何给定时间影响单个帐户。具体来说,由于'balance'是一个int,'withdraw'和'deposit'只是直接改变'balance'的值,使用非原子操作,你永远不会希望'withdraw'和'deposit'的调用被交错.

另一方面,如果您将“余额”表示为 AtomicInteger,则可以安全地交错调用“取款”和“存款”。但是,如果您同时使用“fromLock”和“toLock”,那么最多一个账户可以同时将资金转入任何给定账户。例如。假设账户 B 和 C 都想向账户 A 转账——使用“toLock”,这两个转账必须被序列化。

但退一步说,为什么有必要防止并发存款到给定帐户(假设帐户的余额以线程安全的方式发生变化)?当然,有必要确保任何给定账户在任何给定时间最多可以作为“来自”账户参与一次交易;但我不明白为什么“to”帐户必须存在任何此类约束(再次假设“balance”以线程安全的方式操作)。

因此,除非我在这里遗漏了什么,否则我认为将“余额”表示为 AtomicInteger 并且仅在“转移”中获取“来自”帐户的锁定应该满足您的要求。

注意:在我被否决之前,让我说我的 cmets 仅适用于您的示例代码中表示银行帐户的特定方式(即在执行此类操作的生产系统中,有充分的理由以确保成对的存款和取款在交易中执行)。

【讨论】:

    猜你喜欢
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 2013-01-25
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    相关资源
    最近更新 更多