Название звучит так, как будто впереди много проблем. Вот мой конкретный случай:
Это система продажи проездных билетов. Каждый маршрут имеет ограниченное количество билетов, поэтому покупка последнего билета для данного маршрута не должна быть доступна для двух человек (стандартный сценарий). Однако есть опция «обратный билет». Итак, я использую уникальный идентификатор маршрута (предоставленный базой данных) для следующих действий:
synchronized(bothRoutesUniqueString.intern()) {
synchronized (routeId.intern()) {
if (returnRouteId != null) {
synchronized (returnRouteId.intern()) {
return doPurchase(selectedRoute, selectedReturnRoute);
}
}
return doPurchase(selectedRoute, selectedReturnRoute);
}
}
Два внутренних блока synchronized
предназначены для остановки потоков только в том случае, если билет на этот конкретный маршрут приобретается двумя людьми одновременно, а не в том случае, если билеты на два разных маршрута приобретаются одновременно. , Вторая синхронизация происходит, если, конечно, из-за того, что кто-то может одновременно пытаться приобрести маршрут ретрансляции в качестве исходящего.
Самый внешний блок synchronized
предназначен для сценария, когда два человека покупают одну и ту же комбинацию билетов в обратном порядке. Например, один заказывает Лондон-Манчестер, а другой заказывает Манчестер-Лондон. Если нет внешнего синхронизированного блока, эта ситуация может привести к тупику.
(метод doPurchase()
либо возвращает объект Ticket
, либо выдает исключение, если больше нет доступных билетов)
Теперь я прекрасно понимаю, что это очень неловкое решение, но, если оно работает так, как ожидается, оно дает:
- 10 строк для обработки всего сложного сценария (и с правильными комментариями это будет не так сложно понять)
- нет ненужной блокировки - все блокируется, только если оно должно блокировать.
- агностицизм базы данных
Я также знаю, что такие сценарии обрабатываются либо пессимистичными, либо оптимистичными блокировками базы данных, и, поскольку я использую Hibernate, их тоже не составит труда реализовать.
Я думаю, что горизонтальное масштабирование может быть достигнуто с помощью приведенного выше кода с использованием кластеризации VM. Согласно документации Teracotta , она позволяет превратить одноузловое многопоточное приложение в многоузловое и:
Terracotta отслеживает вызовы String.intern () и гарантирует равенство ссылок для этих явно интернированных строк. Поскольку все ссылки на интернированный объект String указывают на каноническое значение, проверки на равенство ссылок будут работать, как ожидается, даже для распределенных приложений.
Итак, теперь перейдем к самим вопросам:
- замечаете ли вы какие-либо недостатки вышеуказанного кода (кроме его неловкости)?
- существует ли подходящий класс из
java.util.concurrent
API, чтобы помочь в этом сценарии?
- почему блокировка базы данных предпочтительнее для этого?
Обновление:
Поскольку большинство ответов касаются OutOfMemoryError
, я поставил оценку для intern()
, и память не была израсходована. Возможно, таблица строк очищается, но в моем случае это не имеет значения, поскольку мне нужно, чтобы объекты были равны в условиях гонки, и очистка самых последних строк не должна происходить в точке:
System.out.println(Runtime.getRuntime().freeMemory());
for (int i = 0; i < 10000000; i ++) {
String.valueOf(i).intern();
}
System.out.println(Runtime.getRuntime().freeMemory());
P.S. Среда JRE 1.6