Лучшее решение вместо вложенных синхронизированных блоков в Java? - PullRequest
9 голосов
/ 20 октября 2011

У меня есть 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);
    }
}

Но компилятор предупреждает меня о вложенных блоках синхронизации , и я верю, что это плохо?Кроме того, я не очень люблю решение max / min (я не был тем, кто придумал эту идею), и я хотел бы избежать этого, если это возможно.

Как можно решить эти 2 проблемывыше?Если бы мы могли заблокировать более одного объекта, мы бы заблокировали и учетную запись from и to, но мы не можем этого сделать (насколько я знаю).Каково решение тогда?

Ответы [ 5 ]

5 голосов
/ 20 октября 2011

Лично я предпочитаю избегать любого, кроме самого тривиального сценария синхронизации. В случае, подобном вашему, я бы, вероятно, использовал бы синхронизированный сбор очередей для сбора депозитов и вывода в однопоточный процесс, который манипулирует вашей незащищенной переменной. «Интересная» вещь в этих очередях - это когда вы помещаете весь код в объект, который вы помещаете в очередь, чтобы код, извлекающий объект из очереди, был абсолютно тривиальным и универсальным (commandQueue.getNext (). Execute ();) - хотя выполняемый код может быть произвольно гибким или сложным, поскольку для его реализации у него есть целый объект «Command» - это тот тип шаблона, в котором превосходно программируется в стиле ОО.

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

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

2 голосов
/ 20 октября 2011

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

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

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

1 голос
/ 20 октября 2011

Если вы еще этого не сделали, возможно, вы захотите взглянуть на более продвинутые блокирующие пакеты в java.util.concurrent .

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

1 голос
/ 20 октября 2011

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

0 голосов
/ 01 октября 2014

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

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

Пример решения

Account.java

import clojure.lang.Ref;

public class Account {
    private Ref mBalance;

    public Account() {
        mBalance = new Ref(0);
    }

    public void withdraw(int value) {
        mBalance.set(getBalance() - value);
    }

    public void deposit(int value) {
        mBalance.set(getBalance() + value);
    }

    private int getBalance() {
        return (int) mBalance.deref();
    }
}

Bank.java

import clojure.lang.LockingTransaction;

import java.util.*
import java.util.concurrent.Callable;

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

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

    public void transfer(int fromId, int toId, int value) {
        try {
            LockingTransaction.runInTransaction(
                    new Callable() {
                        @Override
                        public Object call() throws Exception {
                            mAccounts.get(fromId).withdraw(value);
                            mAccounts.get(toId).deposit(value);
                            return null;
                        }
                    });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

Зависимости

<dependency>
    <groupId>org.clojure</groupId>
    <artifactId>clojure</artifactId>
    <version>1.6.0</version>
</dependency>
...