Руководство по ThreadLocalRandom в Java

Руководство по ThreadLocalRandom в Java

1. обзор

Генерация случайных значений является очень распространенной задачей. Вот почему Java предоставляет классjava.util.Random.

Однако этот класс плохо работает в многопоточной среде.

Упрощенно говоря, причина низкой производительностиRandom в многопоточной среде связана с конкуренцией, учитывая, что несколько потоков используют один и тот же экземплярRandom.

Чтобы устранить это ограничение,Java introduced the java.util.concurrent.ThreadLocalRandom class in JDK 7 – for generating random numbers in a multi-threaded environment.

Давайте посмотрим, как работаетThreadLocalRandom и как его использовать в реальных приложениях.

2. ThreadLocalRandom большеRandom

ThreadLocalRandom is a combination of ThreadLocal and Random classes, which is isolated to the current thread. Таким образом, он обеспечивает лучшую производительность в многопоточной среде, просто избегая любого одновременного доступа к объектамRandom.

Случайное число, полученное одним потоком, не зависит от другого потока, тогда какjava.util.Random предоставляет случайные числа глобально.

Кроме того, в отличие отRandom,ThreadLocalRandom не поддерживает явную установку начального числа. Вместо этого он переопределяет методsetSeed(long seed), унаследованный отRandom, чтобы всегда выдаватьUnsupportedOperationException при вызове.

Давайте теперь рассмотрим некоторые способы генерации случайных значенийint, long иdouble.

3. Генерация случайных значений с использованиемThreadLocalRandom

Согласно документации Oracle,we just need to call ThreadLocalRandom.current() method, and it will return the instance of ThreadLocalRandom for the current thread. Затем мы можем генерировать случайные значения, вызывая доступные методы экземпляра класса.

Давайте сгенерируем случайное значениеint без каких-либо границ:

int unboundedRandomValue = ThreadLocalRandom.current().nextInt());

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

Вот пример генерации случайного значенияint от 0 до 100:

int boundedRandomValue = ThreadLocalRandom.current().nextInt(0, 100);

Обратите внимание, что 0 - это нижний предел включительно, а 100 - исключительный верхний предел.

Мы можем сгенерировать случайные значения дляlong иdouble, вызвав методыnextLong() иnextDouble() таким же образом, как показано в примерах выше.

В Java 8 также добавлен методnextGaussian() для генерации следующего нормально распределенного значения со средним значением 0,0 и стандартным отклонением 1,0 от последовательности генератора.

Как и в случае с классомRandom, мы также можем использовать методыdoubles(), ints() иlongs() для генерации потоков случайных значений.

4. СравнениеThreadLocalRandom иRandom с использованием JMH

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

Во-первых, давайте создадим пример, в котором все потоки совместно используют один экземплярRandom.. Здесь мы отправляем задачу генерации случайного значения с использованием экземпляраRandom вExecutorService:

ExecutorService executor = Executors.newWorkStealingPool();
List> callables = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 1000; i++) {
    callables.add(() -> {
         return random.nextInt();
    });
}
executor.invokeAll(callables);

Давайте проверим производительность приведенного выше кода с помощью тестирования JMH:

# Run complete. Total time: 00:00:36
Benchmark                                            Mode Cnt Score    Error    Units
ThreadLocalRandomBenchMarker.randomValuesUsingRandom avgt 20  771.613 ± 222.220 us/op

Аналогично, давайте теперь будем использоватьThreadLocalRandom вместо экземпляраRandom, который использует один экземплярThreadLocalRandom для каждого потока в пуле:

ExecutorService executor = Executors.newWorkStealingPool();
List> callables = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    callables.add(() -> {
        return ThreadLocalRandom.current().nextInt();
    });
}
executor.invokeAll(callables);

Вот результат использованияThreadLocalRandom:

# Run complete. Total time: 00:00:36
Benchmark                                                       Mode Cnt Score    Error   Units
ThreadLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom avgt 20  624.911 ± 113.268 us/op

Наконец, сравнивая приведенные выше результаты JMH дляRandom иThreadLocalRandom, мы можем ясно видеть, что среднее время, необходимое для генерации 1000 случайных значений с использованиемRandom, составляет 772 микросекунды, тогда как при использованииThreadLocalRandom это около 625 микросекунд.

Таким образом, можно сделать вывод, чтоThreadLocalRandom is more efficient in a highly concurrent environment.

Чтобы узнать больше оJMH, ознакомьтесь с нашими предыдущимиarticle here.

5. Заключение

В этой статье проиллюстрирована разница междуjava.util.Random иjava.util.concurrent.ThreadLocalRandom.

Мы также увидели преимуществоThreadLocalRandom надRandom в многопоточной среде, а также производительность и то, как мы можем генерировать случайные значения с помощью класса.

ThreadLocalRandom - простое дополнение к JDK, но оно может оказать заметное влияние на приложения с высокой степенью параллелизма.

И, как всегда, реализацию всех этих примеров можно найти вGitHub project.