Улучшение 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 или лучший вариант этого.

Другие вопросы по тегам