Конфликт при одновременном использовании java.util.Random

Документация Oracle Java:

Экземпляры java.util.Random являются потокобезопасными. Однако одновременное использование одного и того же экземпляра java.util.Random через потоки может столкнуться с конкуренцией и, как следствие, низкой производительностью. Вместо этого используйте ThreadLocalRandom в многопоточных конструкциях.

Что может быть причиной низкой производительности?

Ответ 1

Внутри java.util.Random хранит AtomicLong с текущим семенем, и всякий раз, когда запрашивается новое случайное число, в обновлении семени возникает проблема.

Из реализации java.util.Random:

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

С другой стороны, ThreadLocalRandom гарантирует, что семя обновляется без каких-либо конфликтов, имея одно семя в потоке.

Ответ 2

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

ThreadLocalRandom можно использовать вместо этого, чтобы обеспечить прозрачное сквозное инстанцирование, чтобы гарантировать, что внутреннее состояние обновляется на основе потока, таким образом избегая блокировки.

Обратите внимание, что если выполняется правильно, операция обновления AtomicLong не должна выполняться ужасно, если вы не используете огромное количество потоков, так как по существу можно оптимизировать ее в JVM до чего-то вроде lock xchg на x86, Основная вычислительная стоимость за пределами блокировки, вероятно, представляет собой комбинацию длинного умножения и вращательного сдвига.