ThreadLocalRandom: примеры (JAVA)
ThreadLocalRandomОписание класса ThreadLocalRandom
ThreadLocalRandom - это класс из пакета java.util.concurrent, предназначенный для генерации случайных чисел в многопоточных средах. В отличие от java.util.Random, который при совместном использовании несколькими потоками может приводить к конкуренции (contention) и снижению производительности, ThreadLocalRandom использует отдельный экземпляр генератора для каждого потока. Таким образом, вызовы методов не синхронизируются и не блокируют другие потоки.
Когда используется: класс применяется в высоконагруженных параллельных вычислениях, в параллельных стримах, в задачах с большим количеством потоков, где требуется большое количество случайных чисел. Он предпочтительнее Random и Math.random() при работе с многопоточностью.
Аргументы методов: Основные методы не принимают аргументов (например, nextInt()) или принимают один целочисленный аргумент - верхнюю границу (исключительно) для равномерного распределения. Также есть перегруженные версии с двумя аргументами: origin (включительно) и bound (исключительно) для генерации чисел в произвольном диапазоне. Для вещественных чисел nextDouble(), nextFloat() могут принимать один или два аргумента. nextBoolean() аргументов не имеет. nextLong() работает аналогично nextInt(). nextGaussian() возвращает нормально распределённое значение с математическим ожиданием 0 и стандартным отклонением 1. nextBytes(byte[]) заполняет переданный массив случайными байтами.
Возвращаемые значения: Все методы возвращают примитивные типы: int, long, double, float, boolean, byte[] (заполняется), double для гауссова распределения.
Для получения экземпляра используется статический метод ThreadLocalRandom.current(). Экземпляр не нужно хранить долго - он автоматически связывается с текущим потоком.
Примеры использования
import java.util.concurrent.ThreadLocalRandom;
public class Example {
public static void main(String[] args) {
// Случайное целое число
int randomInt = ThreadLocalRandom.current().nextInt();
System.out.println("nextInt(): " + randomInt);
// Целое от 0 до 9 (исключительно)
int bounded = ThreadLocalRandom.current().nextInt(10);
System.out.println("nextInt(10): " + bounded);
// Целое от 5 до 15 (5 включительно, 15 исключительно)
int ranged = ThreadLocalRandom.current().nextInt(5, 15);
System.out.println("nextInt(5,15): " + ranged);
// Случайное double [0,1)
double randomDouble = ThreadLocalRandom.current().nextDouble();
System.out.println("nextDouble(): " + randomDouble);
// Double от 0 до 5 (исключительно)
double boundedDouble = ThreadLocalRandom.current().nextDouble(5.0);
System.out.println("nextDouble(5.0): " + boundedDouble);
// Double от 2.5 до 7.3
double rangedDouble = ThreadLocalRandom.current().nextDouble(2.5, 7.3);
System.out.println("nextDouble(2.5,7.3): " + rangedDouble);
// Случайный boolean
boolean bool = ThreadLocalRandom.current().nextBoolean();
System.out.println("nextBoolean(): " + bool);
// Гауссово распределение (нормальное)
double gaussian = ThreadLocalRandom.current().nextGaussian();
System.out.println("nextGaussian(): " + gaussian);
// Заполнение массива байтов
byte[] bytes = new byte[6];
ThreadLocalRandom.current().nextBytes(bytes);
System.out.print("nextBytes(): ");
for (byte b : bytes) System.out.print(b + " ");
System.out.println();
}
}nextInt(): -1234567890 nextInt(10): 7 nextInt(5,15): 11 nextDouble(): 0.6738294567839123 nextDouble(5.0): 3.294851234678912 nextDouble(2.5,7.3): 5.912345678901234 nextBoolean(): true nextGaussian(): -0.3528765432109876 nextBytes(): 45 -123 78 0 12 -56
Примечание: фактические значения при каждом запуске будут разными.
Альтернативные классы для генерации случайных чисел в Java
java.util.Random - базовый класс. При использовании из нескольких потоков требуется синхронизация, что снижает производительность. Подходит для однопоточных сценариев или низкой нагрузки.
java.security.SecureRandom - криптостойкий генератор. Используется для задач безопасности (генерация ключей, токенов). Работает медленнее, чем ThreadLocalRandom, но выдаёт непредсказуемые последовательности.
java.util.SplittableRandom - ещё один генератор, предназначенный для параллельных вычислений. Он позволяет создавать «расщеплённые» экземпляры, независимые друг от друга. Хорошо подходит для параллельных стримов и комбинированных задач, где нужно раздавать генераторы ветвям вычислений.
Math.random() - удобный, но синхронизированный вызов. Внутри использует один экземпляр Random. Не подходит для многопоточных высоконагруженных систем.
Выбор зависит от контекста: для многопоточных сценариев с высокой нагрузкой ThreadLocalRandom - оптимальный вариант; для криптографии - SecureRandom; для параллельных алгоритмов с «ветвлением» - SplittableRandom.
Аналоги ThreadLocalRandom в других языках программирования
PHP: функции
rand(),mt_rand()не являются потокобезопасными. Для многопоточных сред (pthreads) используются отдельные экземпляры генератора.random_int()криптостойкий.// PHP – каждый поток создаёт свой экземпляр $random = new Random\Randomizer(); echo $random->getInt(0, 10); // 7JavaScript:
Math.random()- единственный стандартный генератор. Он не является потокобезопасным в среде Node.js (используется один поток). Для многопоточности (Worker) каждый воркер вызывает свой Math.random().// JavaScript (Node.js) const r = Math.floor(Math.random() * 10); // 3Python: модуль
random- использует глобальное состояние (Mersenne Twister). Для потокобезопасности нужно создавать отдельные объектыRandomв каждом потоке.secretsдля криптостойкости.import random r = random.Random() print(r.randint(0, 10)) # 7SQL:
RANDOM()(PostgreSQL) илиNEWID()(SQL Server) не привязаны к потокам. Каждая запись получает своё случайное число.SELECT floor(random() * 10)::int; -- PostgreSQLC#: класс
System.Randomне является потокобезопасным. Рекомендуется использоватьThreadLocal<Random>илиRandom.Shared(начиная с .NET 6).// C# .NET 6+ int r = Random.Shared.Next(10); // 4Lua:
math.random()- глобальное состояние. Для многопоточности (Lua-lanes) нужно изолировать состояния.-- Lua math.randomseed(os.time()) print(math.random(1,10)) -- 8Go: пакет
math/rand- глобальный генератор с мьютексом (потокобезопасен, но медленнее).rand.NewSourceсоздаёт независимые источники. Для потокобезопасности без блокировок -rand.New(&lockedSource).// Go import "math/rand" r := rand.New(rand.NewSource(42)) println(r.Intn(10)) // 2Kotlin: использует Java-классы.
ThreadLocalRandom.current()доступен напрямую. Также естьkotlin.random.Random, который под капотом использует ThreadLocalRandom.// Kotlin import kotlin.random.Random println(Random.nextInt(10)) // 7
Главное отличие всех этих реализаций от Java ThreadLocalRandom - отсутствие автоматической привязки к потоку с минимальными накладными расходами. Во многих языках необходимо вручную создавать и хранить экземпляры для каждого потока.
Типичные ошибки при использовании ThreadLocalRandom
Забыли вызвать current():
ThreadLocalRandom- статический класс, экземпляр получается черезThreadLocalRandom.current()каждый раз при вызове. Если сохранить ссылку и использовать её в другом потоке, она будет работать неправильно (всё равно будет связана с исходным потоком).// Неправильно: ThreadLocalRandom rnd = ThreadLocalRandom.current(); new Thread(() -> System.out.println(rnd.nextInt())).start(); // rnd всё ещё от родительского потока!Ошибка: генератор может отработать, но задумка нарушена; лучше вызывать current() в дочернем потоке.
Путаница с границами: в методах вида
nextInt(origin, bound)нижняя граница включительна, верхняя - исключительна. Ошибка - думать, что обе включительны.int val = ThreadLocalRandom.current().nextInt(10, 20); // 10..19, не 20 System.out.println(val); // может быть 19, но не 20Невозможность установки seed: ThreadLocalRandom не позволяет задавать начальное значение (seed) для воспроизводимости. Если нужна детерминированная последовательность, используйте
Randomс известным seed.Использование в однопоточных сценариях: в однопоточном коде ThreadLocalRandom работает, но
Randomможет быть быстрее из-за отсутствия oveголовов на извлечение из ThreadLocal. Однако разница незначительна.Передача экземпляра между потоками: сохранение
ThreadLocalRandomв поле объекта и использование из разных потоков - ошибка. Всегда получайте текущий экземпляр внутри потока.
Изменения в ThreadLocalRandom в последних версиях Java
Java 7: класс был введён в составе пакета
java.util.concurrent. Базовая функциональность:current(),nextInt(),nextDouble(),nextLong(),nextBoolean(),nextFloat(),nextGaussian().Java 8: добавлены методы
ints(),longs(),doubles(), возвращающие неограниченные или ограниченные потоки (Stream) случайных чисел. Это позволило интегрировать генерацию с параллельными стримами без явного вызоваcurrent()в каждом элементе.Java 17 и 21: существенных изменений не произошло. Класс остаётся стабильным. Оптимизирована внутренняя реализация (например, использование
VarHandleвместоUnsafeв более ранних версиях).Будущие версии: ожидается, что функциональность останется неизменной. Разработчикам рекомендуется использовать
ThreadLocalRandomдля новых многопоточных проектов, где не требуется криптостойкость.
Расширенные и нестандартные примеры использования
1. Использование в параллельных стримах (Parallel Streams)
import java.util.stream.IntStream;
import java.util.concurrent.ThreadLocalRandom;
public class ParallelStreamExample {
public static void main(String[] args) {
long sum = IntStream.range(0, 1000)
.parallel()
.map(i -> ThreadLocalRandom.current().nextInt(100))
.sum();
System.out.println("Сумма 1000 случайных чисел: " + sum);
}
}Сумма 1000 случайных чисел: 49237
Пояснение: каждый элемент стрима обрабатывается в своём потоке, и вызов ThreadLocalRandom.current() возвращает экземпляр, привязанный к этому потоку. Благодаря этому нет конкуренции.
2. Генерация случайного массива байтов заданной длины
import java.util.concurrent.ThreadLocalRandom;
public class RandomBytesArray {
public static void main(String[] args) {
byte[] arr = new byte[100];
ThreadLocalRandom.current().nextBytes(arr);
// arr теперь заполнен случайными байтами
System.out.println("Первый байт: " + arr[0]);
}
}Первый байт: -82
3. Генерация случайной строки (алфавитно-цифровая)
import java.util.concurrent.ThreadLocalRandom;
public class RandomString {
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
public static String generateRandomString(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int index = ThreadLocalRandom.current().nextInt(ALPHABET.length());
sb.append(ALPHABET.charAt(index));
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(generateRandomString(10));
}
}aB3kLqW9Xz
4. Комбинирование с CompletableFuture (асинхронные задачи)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
public class CompletableFutureExample {
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
// Этот код выполняется в потоке ForkJoinPool.commonPool()
return ThreadLocalRandom.current().nextInt(1, 100);
});
System.out.println("Случайное число: " + future.get());
}
}Случайное число: 54
5. Использование с ForkJoinPool
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ThreadLocalRandom;
class RandomSumTask extends RecursiveTask<Integer> {
private int threshold;
RandomSumTask(int threshold) { this.threshold = threshold; }
@Override
protected Integer compute() {
if (threshold <= 1) {
return ThreadLocalRandom.current().nextInt(10);
}
RandomSumTask subTask = new RandomSumTask(threshold / 2);
subTask.fork();
int result = ThreadLocalRandom.current().nextInt(10);
return result + subTask.join();
}
}
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool pool = ForkJoinPool.commonPool();
int sum = pool.invoke(new RandomSumTask(4));
System.out.println("Сумма случайных чисел: " + sum);
}
}Сумма случайных чисел: 23
6. Использование методов-потоков (ints, longs, doubles) с ограничением
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.IntStream;
public class StreamMethodsExample {
public static void main(String[] args) {
// Бесконечный поток целых чисел от 0 до 100, взято 10 первых
IntStream stream = ThreadLocalRandom.current().ints(0, 100);
stream.limit(10).forEach(System.out::println);
// Поток из ровно 5 чисел
ThreadLocalRandom.current().ints(5, 1, 50)
.forEach(System.out::println);
}
}23 87 12 45 9 67 34 11 78 6 --- (второй набор) 7 42 19 33 28
7. Использование в лямбда-выражениях без явного хранения экземпляра
import java.util.function.Supplier;
import java.util.concurrent.ThreadLocalRandom;
public class LambdaExample {
public static void main(String[] args) {
Supplier<Integer> randomSupplier = () -> ThreadLocalRandom.current().nextInt(1, 100);
System.out.println(randomSupplier.get());
System.out.println(randomSupplier.get());
}
}34 78
Пояснение: при каждом вызове get() из лямбды вызывается ThreadLocalRandom.current(), что безопасно при параллельном выполнении (если supplier будет передан в parallel stream).