Улучшение Hi-Lo Id генератора
У меня есть Hi-Lo Id генератор, который я использую в многопоточной среде. Генератор может вызываться до 100 тыс. Раз в секунду на поток
У меня достаточно хорошая (и безопасная) реализация, которая отлично работает. IdAllocator - это объект, который выбирает следующий "пакет" идентификаторов. Вы можете предположить, что это потокобезопасно. Я также установил batchSize довольно высоко (1 миллион)
private final IdAllocator idAllocator;
private final String idKey;
private final int batchSize;
private final Object lock = new Object();
private long currentId = 0;
private long nextFetchId = -1;
IdGeneratorImpl( IdAllocator idAllocator, String idKey, int batchSize )
{
this.idAllocator = idAllocator;
this.idKey = idKey;
this.batchSize = batchSize;
}
public long getNextId()
{
synchronized ( lock )
{
if ( currentId < nextFetchId )
{
long localCurrent = currentId;
currentId++;
return localCurrent;
}
currentId = idAllocator.allocateBatch( idKey, batchSize );
nextFetchId = currentId + batchSize;
return getNextId();
}
}
В настоящее время я в основном, но не всегда, использую это без всякой поддержки. Однако в будущем он будет вызываться несколькими потоками.
Я рассмотрел создание одного экземпляра для каждого потока, что, вероятно, будет лучшим подходом. Однако, как интеллектуальный / обучающий опыт, я задавался вопросом, могу ли я улучшить эту реализацию, в частности, чтобы уменьшить потенциальный конфликт в getNextId(), когда несколько потоков вызывают его часто?
2 ответа
Если вы посмотрите на спящий TableHiLoGenerator
, он использует простой synchronized
в методе генерации, что означает, что несколько потоков будут ожидать, так что только один из них будет выполнять метод одновременно. Вы сделали то же самое с вашим lock
(что немного избыточно - synchronized
метод делает то же самое). Итак, я бы пришел к выводу, что ваши реализации в порядке.
Могу поспорить, вы слишком оптимистичны. Размер пакета в один миллион означает, что вы попадаете в БД при каждой миллионной вставке. Если бы вы использовали тысячу вместо этого, вы бы не увидели разницы в производительности и потратили бы гораздо меньше идентификаторов. Конечно, с long
это не имеет большого значения.
Выполнение вышеуказанных действий в каждом потоке будет означать, что вы тратите до миллиона идентификаторов на поток. Ничего страшного с long
, Я знаю.
Это также увеличит стоимость запуска без необходимости и, возможно, оставит много локального мусора. Опять же, ничего страшного, но в чем преимущество? Короткий код, подобный приведенному выше, занимает несколько наносекунд, а доступ к БД занимает на много порядков больше. Таким образом, никакие разногласия не могут действительно замедлить вас.
Тем не менее, правильный способ оптимизировать это, вероятно, через AtomicLong
, Это имеет гораздо меньшую нагрузку, чем синхронизация. использование getAndIncrement
и только ввести синхронизированный блок, когда вы заполняете nextFetchId
, В этом блоке сначала перепроверьте, выполнил ли другой поток работу, которую вы собираетесь выполнить, и если нет, получите следующий идентификатор из базы данных.
Гораздо проще использовать спящий режим HiLoOptimizer
или лучший вариант этого.