Лучшие способы написать метод, который обновляет два объекта в многопоточной среде Java? - PullRequest
6 голосов
/ 02 июня 2010

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

AccountService определяется как

interface AccountService{
 public void debit(account);
 public void credit(account);
 public void transfer(Account account, Account account1);

}

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

Меня интересуют ответы, которые ссылаются на код Java 1.4, а также ответы, которые могут использовать ресурсы из java.util.concurrent в Java 5

Ответы [ 5 ]

11 голосов
/ 02 июня 2010

Синхронизируйте оба объекта Account и выполните передачу. Убедитесь, что вы всегда синхронизируете в одном и том же порядке. Для этого Account s реализуйте Comparable, отсортируйте две учетные записи и синхронизируйте в этом порядке.

Если вы не заказываете счета, вы запускаете возможность взаимоблокировки, если один поток переходит от A к B, а другой - с B на A.

Этот точный пример обсуждается на странице 207 Java Concurrency in Practice , критической книги для любого, кто занимается многопоточной разработкой Java. Пример кода доступен на веб-сайте издателя :

3 голосов
/ 02 июня 2010

Классический пример, очень хорошо объясненный здесь - http://www.javaworld.com/javaworld/jw-10-2001/jw-1012-deadlock.html?page=4

2 голосов
/ 02 июня 2010

Возможно, вам нужна полная поддержка транзакций (если, конечно, это реальное приложение).

Сложность решения вряд ли зависит от вашей среды. Подробно опишите вашу систему, и мы постараемся вам помочь (какое приложение? Использует ли он веб-сервер? Какой веб-сервер? Что используется для хранения данных? И т. Д.)

1 голос
/ 02 июня 2010

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

Если другие методы также могут получить доступ к AccountService, вы можете решить, чтобы все они использовали одну глобальную блокировку. Простой способ сделать это - заключить весь код, который обращается к AccountService, в синхронизированный блок (X) {...}, где X - это некоторый экземпляр общего / одноэлементного объекта (который может быть самим экземпляром AccountService). Это будет потокобезопасным, потому что только один поток будет одновременно получать доступ к AccountService, даже если они используют разные методы.

Если этого по-прежнему недостаточно, вам придется использовать более сложные подходы к блокировке. Один из распространенных подходов заключается в индивидуальной блокировке учетных записей перед их изменением ... но тогда вы должны быть очень осторожны, чтобы устанавливать блокировки в согласованном порядке (например, по идентификатору учетной записи), в противном случае вы попадете в тупики.

Наконец, если AccountService является удаленной службой, вы попадаете на территорию распределенной блокировки .... если у вас нет докторской степени в области компьютерных наук и многолетнего бюджета на исследования, вам, вероятно, следует избегать этого.

0 голосов
/ 02 июня 2010

Разве вы не можете избежать синхронизации, используя AtomicReference<Double> для баланса счета вместе с get() и set()?

...