Распределение случайных чисел
у меня есть два варианта кода:
1
int myFunc() {
return new Random().nextInt();
}
или:
2
private static final Random random = new Random();
int myFunc() {
return random.nextInt();
}
Я понимаю, что option 2
более идиоматические. Я задаюсь вопросом о действительности option 1
.
на option 1
Я буду использовать только первое число, сгенерированное данным семенем. В option 2
Я выбираю семя и создать n
числа, использующие это семя. IIUC гарантии на рандоме на такое использование случай.
мой вопрос, следовательно, если я позвоню option 1
много времен там все гарантии на единообразии распределения выхода?
5 ответов
мой реальный вопрос заключается в том, Вариант 1 является математически корректным.
начнем с варианта 2. Генератор случайных чисел, используемый java.util.Random
указывается в javadoc следующим образом:
класс использует 48-разрядное семя, которое изменяется, используя линейную congruential формулу. (См. Дональд Кнут, Искусство программирования, Том 2, раздел 3.2.1.)
и более конкретная деталь в различных методах" документация Javadoc.
но дело в том, что мы используем последовательность, генерируемая линейную congruential формулу, и такие формулы имеют значительную степень автокорреляции ... что может быть проблематично.
теперь с опцией 1 вы используете другой Random
экземпляр с новым семенем каждый раз и применение одного раунда формулы LC. Таким образом, вы получаете последовательность чисел, которые, вероятно, будут автокоррелированы с семенами. Однако семена образуются в различными способами, в зависимости от версии Java.
Java 6 делает это:
public Random() { this(++seedUniquifier + System.nanoTime()); }
private static volatile long seedUniquifier = 8682522807148012L;
... что совсем не случайно. Если вы создали Random
экземпляры с постоянным интервалом семена, вероятно, будут расположены близко друг к другу, и поэтому последовательность случайных чисел, производимых вашим вариантом № 1, может быть автоматически коррелирована.
напротив, Java 7 и 8 делают это:
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
private static final AtomicLong seedUniquifier
= new AtomicLong(8682522807148012L);
последовательность семян, произведенных выше, вероятно, будет намного лучшее приближение к (истинной) случайности. Это, вероятно, делает ваш вариант № 1 лучше варианта №2.
недостатком вашего варианта #1 в Java 6 через 8 является то, что System.nanoTime()
вероятно, вызов включает в себя системный вызов. Это относительно дорого.
таким образом, короткий ответ заключается в том, что это конкретная версия Java, которая из опции #1 и опции #2 производит более качественные "случайные" числа ... с математической точки зрения.
в обоих случаях распределение чисел будет равномерным по достаточно большому размеру выборки, хотя я не уверен, что имеет смысл говорить о вероятностных распределениях, когда процесс детерминирован.
однако ни один из подходов не подходит в качестве генератора случайных чисел "crypto strength".
Быстрый Код:
// For occasional tasks that just need an average quality random number
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute( () -> {
ThreadLocalRandom.current().nextInt(); // Fast and unique!
} );
// For SecureRandom, high quality random number
final Random r = new SecureRandom();
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute( () -> {
r.nextInt(); // sun.security.provider.NativePRNG uses singleton. Can't dodge contention.
} );
// Apache Common Math - Mersenne Twister - decent and non-singleton
int cpu = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool( cpu );
Map<Thread, RandomGenerator> random = new WeakHashMap<>( cpu, 1.0f );
executor.execute( ()-> {
RandomGenerator r;
synchronized ( random ) { // Get or create generator.
r = random.get( Thread.currentThread() );
if ( r == null ) random.put( Thread.currentThread(), r = new MersenneTwister() );
}
r.nextInt( 1000 );
} );
объяснение:
-
два
Random
того же семени даст те же числа.- таким образом, мы сосредоточимся на том, можем ли мы гарантировать различные семена.
-
в теории,
new Random()
в каждом потоке не гарантирует, разных семян.- новый случайный заполнена nanoTime и "уникальный" число.
- число не гарантируется уникальным, поскольку его расчет не синхронизирован.
- что касается nanoTime, он гарантирует, что " по крайней мере, хорошо, как currentTimeMillis"
- currentTimeMillis ничего не гарантирует и может быть довольно грубой.
- в реальной жизни два раза одинаковы только на старые системы linux и Win 98.
-
на практике
new Random()
в каждом потоке в основном всегда получают разные семена.- создание потока-это дорого. Шахта создает 1 на 50 000 НС. И это не медленно.
- 50μs путь над зернистостями nanoTime общими до несколько десятков НС.
- вычисление уникального номера (1.2) также быстро, поэтому получать такие же число очень редко.
- использовать исполнители создать нить бассейн чтобы избежать тяжелых новых потоков над головой.
-
zapl предложил
ThreadLocalRandom.current().nextInt()
. Великая идея.- он не создает новый
Random
, а также линейный конгруэнтный генератор. - он генерирует новый случайный для каждого потока вызовов, поскольку этот поток семя.
- оно построен для того чтобы быть очень быстр в multi-потоке. (См. Примечания ниже.)
- он статически засеян
SecureRandom
, которые производят более качественные случайные числа.
- он не создает новый
-
"uniformally распространяется" - это только одна небольшая часть случайность тесты.
-
Random
is несколько униформу, и его результат может быть предсказал дали всего две ценности. -
SecureRandom
гарантии этого не происходит. (т. е. криптографически сильный) - нет никакого риска столкновения семян, Если вы создадите новый
SecureRandom
в каждом потоке. - но в настоящее время его источник один поток во всяком случае, нет параллельного поколения.
- для хорошего RNG, который поддерживает многопоточность, найдите внешняя помощь как Apache Common MT.
-
Примечание: детали реализации выведены из исходного кода Java 8. Будущая версия Java может измениться; например,
ThreadLocalRandom
используетsun.misc.Unsafe
хранить семена, который могут быть удалены в Java 9 заставляя ThreadLocalRandom найти новый способ работы без разногласий.
нет.
нет никаких гарантий в отношении свойств распределения чисел, которые будут получены с помощью опции 1. Как было ясно в других ответах, реализация конструктора для java.util.Random
зависит от системного времени. Таким образом, для того, чтобы сделать гарантию на свойства распределения номеров, которые вы получаете с вариантом 1, вам нужно будет иметь возможность сделать гарантии о распределении номеров, произведенных звонками программа позволяет получить системное время на любой платформе, где программа будет работать.
С вариантом 2, однако, есть математические гарантии, которые могут быть сделаны о распределении чисел, которые будут произведены во время одного выполнения программы. С линейным конгруэнтным генератором (алгоритм генерации псевдослучайных чисел, используемый java.util.Random
) некоторые свойства случайности не так хороши, как с другими алгоритмами, но распределение гарантировано быть относительно однородным.
это не обязательно означает, что Вариант 1 не может служить вашим целям. Это зависит от того, что вы делаете.
Java инициализирует случайное семя с System.nanoTime()
и последовательный счетчик. Это дает некоторую гарантию того, что семя будет отличаться для каждого вызова, хотя я бы воздержался от его криптографической защиты.
С точки зрения производительности-вы действительно ожидаете, что блокировка внутреннего состояния Random в опции 1 будет иметь большую производительность, чем все следующее:
- доступ и увеличение volatile долго
- получение текущего системного времени (что довольно дорого)
- динамическое выделение
- еще один объект для сбора мусора
мое предложение будет делать бенчмарки вашего реального приложения, чтобы узнать, но я ожидаю, что Вариант 1 будет самым медленным из всех трех.
по моему опыту, лучший баланс между хорошим распределением и производительностью дается с помощью чего-то вроде генератора "Messerne Twister"(см. В разделе Apache Commons) . Для еще более причудливого решения см. этой.