更好的解决方案而不是Java中的嵌套同步块?

Ric*_*ral 9 java concurrency synchronized

我有一个Bank列表的类Account.银行有一种transfer()方法可以将价值从一个帐户转移到另一个帐户.我们的想法是在转移中锁定fromto帐户.

要解决这个问题,我有以下代码(请记住,这是一个非常简单的例子,因为它只是一个例子):

public class Account {
    private int mBalance;

    public Account() {
        mBalance = 0;
    }

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

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

public class Bank {
    private List<Account> mAccounts;
    private int mSlots;

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

    public void transfer(int fromId, int toId, int value) {
        synchronized(mAccounts.get(fromId, toId)) {
            synchronized(mAccounts.get(toId)) {
                mAccounts.get(fromId).withdraw(value);
                mAccounts.get(toId).deposit(value);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这有效,但不能防止死锁.要解决此问题,我们需要将同步更改为以下内容:

synchronized(mAccounts.get(Math.min(fromId, toId))) {
    synchronized(mAccounts.get(Math.max(fromId, toId))) {
        mAccounts.get(fromId).withdraw(value);
        mAccounts.get(toId).deposit(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

但编译器警告我嵌套同步块,我相信这是一件坏事吗?另外,我不太喜欢最大/最小解决方案(我不是那个提出这个想法的人),如果可能的话,我想避免这种情况.

如何解决上述两个问题?如果我们可以锁定多个对象,我们会锁定fromto帐户,但我们不能这样做(据我所知).那么解决方案是什么?

Bil*_*l K 5

我个人更喜欢避免最琐碎的同步方案。在像您这样的情况下,我可能会使用同步队列集合来将存款转移到一个单线程进程中,该进程将处理您不受保护的变量。这些队列的“乐趣”在于将所有代码放入放入队列的对象中,因此从队列中拉出对象的代码绝对是琐碎且通用的(commandQueue.getNext()。execute();)。 -但是正在执行的代码可以任意灵活或复杂,因为它具有用于实现的完整“ Command”对象–这是OO风格编程最擅长的一种模式。

这是一个很棒的通用解决方案,可以解决很多线程问题而无需显式同步(同步仍然存在于队列中,但是同步通常很少并且没有死锁,通常只有“ put”方法需要完全同步,并且这是内部的)。

解决某些线程问题的另一种方法是确保您可能写入的每个共享变量只能由单个进程“写入”,然后通常可以完全取消同步(尽管您可能需要分散一些瞬态)


alf*_*alf 2

锁定顺序确实是解决方案,所以你是对的。编译器会警告您,因为它无法确保所有锁定都是有序的\xe2\x80\x94,它不够聪明,无法检查您的代码,也不够聪明,无法知道可能还有更多锁定。

\n\n

另一种解决方案可以是锁定封闭对象,例如,对于一个用户帐户内的转账,您可以锁定用户。用户之间的传输并非如此。

\n\n

话虽如此,您可能不会依赖 Java 锁定来进行传输:您需要一些数据存储,通常是数据库。如果使用数据库,锁定将转移到存储。尽管如此,同样的原则仍然适用:您订购锁以避免死锁;您升级锁以使锁定更简单。

\n