Лучшее решение вместо вложенных синхронизированных блоков в Java?

у меня есть Bank класс со списком Account. У банка есть transfer() способ переноса значения с одного счета на другой. Идея состоит в том, чтобы заблокировать оба from и to счета в течение передачи.

чтобы решить эту проблему у меня есть следующий код (Пожалуйста, имейте в виду, что это очень тривиальный пример, потому что это просто пример):

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);
            }
        }
    }
}

это работает, но не мешает взаимоблокировок. Чтобы исправить это, нам нужно изменить синхронизация со следующим:

synchronized(mAccounts.get(Math.min(fromId, toId))) {
    synchronized(mAccounts.get(Math.max(fromId, toId))) {
        mAccounts.get(fromId).withdraw(value);
        mAccounts.get(toId).deposit(value);
    }
}

но компилятор предупреждает меня о вложенные блоки синхронизации

5 ответов


порядок блокировки действительно является решением, поэтому вы правы. Компилятор предупреждает вас, потому что он не может убедится все ваша блокировка заказана-она недостаточно умна, чтобы проверить ваш код, и достаточно умна, чтобы знать, что может быть больше.

альтернативным решением может быть блокировка заключительного объекта, например, для переводов внутри учетной записи одного пользователя вы можете заблокировать пользователя. Не так с передачами между пользователями.

сказав это, вы, вероятно, не собираетесь полагаться на блокировку Java для передачи: вам нужно некоторое хранилище данных, обычно база данных. В случае использования базы данных блокировка перемещается в хранилище. Тем не менее, применяются те же принципы: вы заказываете замки, чтобы избежать тупиков; вы увеличиваете замки, чтобы упростить блокировку.


Я лично предпочитаю избегать любого, кроме самого тривиального сценария синхронизации. В случае, подобном вашему, я бы, вероятно, использовал синхронизированную коллекцию очередей для воронки депозитов и вывода в однопоточный процесс, который манипулирует вашей незащищенной переменной. "Забавная" вещь об этих очередях - это когда вы помещаете весь код в объект, который вы бросаете в очередь, поэтому код, вытаскивающий объект из очереди, абсолютно тривиален и универсален (commandQueue.то getnext().execute ();)-тем не менее выполняемый код может быть произвольно гибким или сложным, потому что он имеет целый объект "Command" для его реализации-это тот тип шаблона, в котором выделяется программирование в стиле OO.

Это отличное решение общего назначения и может решить довольно много проблем с потоками без явной синхронизации (синхронизация все еще существует внутри вашей очереди, но обычно минимальна и свободна от взаимоблокировки, часто только метод" put " должен будьте синхронизированы вообще, и это внутреннее).

другое решение некоторых проблем с потоками-убедиться, что каждая общая переменная, в которую вы могли бы написать, может быть "записана" только одним процессом, тогда вы можете вообще отказаться от синхронизации (хотя вам может потребоваться разбросать несколько переходных процессов)


Я бы посоветовал вам изучить объекты блокировки в java. Посмотрите на объекты condition тоже. Каждый объект вашей учетной записи может предоставить условие, при котором поток ожидает. После завершения транзакции вызываются объекты условий await или notify.


Если вы еще не сделали, вы можете посмотреть на более продвинутые пакеты блокировки в java.утиль.параллельный.

в то время как вам все еще нужно позаботиться о том, чтобы избежать взаимоблокировки, ReadWriteLocks, в частности, полезны, чтобы разрешить многопоточный доступ для чтения, все еще блокируя модификацию объекта.


сделать это легко с Полиглот программирования используйте Программная Транзакционная С Clojure а в Java.

программная транзакционная память (STM) - это метод управления параллелизмом аналогично транзакции базы данных для управления доступом к общим память в параллельных вычислениях. Это альтернатива синхронизации на основе блокировки.

пример решение