Ответ 1
Как можно больше, семена для RNG должны быть случайными. Семена, которые вы используете, будут отличаться только в одном или двух битах.
Очень редко есть хорошая причина для создания двух отдельных ГСЧ в одной программе. Ваш код не является одной из тех ситуаций, где это имеет смысл.
Просто создайте один RNG и повторно используйте его, тогда у вас не будет этой проблемы.
В ответ на комментарий mmyers:
Вы случайно не знаете java.util.Random достаточно хорошо, чтобы объяснить, почему он выбирает 5 и 6 в этом случае?
Ответ находится в исходном коде для java.util.Random, который является линейным конгруэнтным RNG. Когда вы указываете семя в конструкторе, его обрабатывают следующим образом.
seed = (seed ^ 0x5DEECE66DL) & mask;
Если маска просто сохраняет нижние 48 бит и отбрасывает остальные.
При генерации фактических случайных бит это семя обрабатывается следующим образом:
randomBits = (seed * 0x5DEECE66DL + 0xBL) & mask;
Теперь, если вы считаете, что семена, используемые Parker, были последовательными (0 -1499), и они использовались один раз, а затем отбрасывались, первые четыре семени генерировались следующие четыре набора случайных бит:
101110110010000010110100011000000000101001110100
101110110001101011010101011100110010010000000111
101110110010110001110010001110011101011101001110
101110110010011010010011010011001111000011100001
Обратите внимание, что первые 10 бит являются indentical в каждом случае. Это проблема, потому что он только хочет генерировать значения в диапазоне 0-7 (для этого требуется всего несколько бит), а реализация RNG делает это, сдвигая старшие биты вправо и отбрасывая низкие биты. Он делает это, потому что в общем случае старшие биты более случайны, чем младшие бит. В этом случае они не потому, что данные семян были плохими.
Наконец, чтобы увидеть, как эти биты преобразуются в десятичные значения, которые мы получаем, вам нужно знать, что java.util.Random делает особый случай, когда n является степенью 2. Он запрашивает 31 случайный бит (верхний 31 бит из вышеперечисленного 48), умножает это значение на n и затем сдвигает его на 31 бит вправо.
Умножение на 8 (значение n в этом примере) совпадает с сдвигом влево на 3 места. Таким образом, чистый эффект этой процедуры заключается в том, чтобы сдвинуть 31 бит на 28 мест вправо. В каждом из 4 приведенных выше примеров это оставляет бит 101 (или 5 в десятичной форме).
Если бы мы не отбросили RNG после одного значения, мы увидели бы, что последовательности расходятся. В то время как четыре последовательности выше всего начинаются с 5, второе значение каждого из них равно 6, 0, 2 и 4 соответственно. Небольшие различия в исходных семенах начинают оказывать влияние.
В ответ на обновленный вопрос: java.util.Random является потокобезопасным, вы можете делиться один экземпляр для нескольких потоков, поэтому все еще нет необходимости иметь несколько экземпляров. Если вам действительно нужно иметь несколько экземпляров RNG, убедитесь, что они засеяны полностью независимо друг от друга, в противном случае вы не можете доверять независимым выводам.
Что касается того, почему вы получаете такие эффекты, java.util.Random не лучший RNG. Это просто, довольно быстро и, если вы не смотрите слишком близко, разумно случайны. Однако, если вы запустите на своем выходе несколько серьезных тестов, вы увидите, что это испорчено. Вы можете увидеть это визуально здесь.
Если вам нужен более случайный RNG, вы можете использовать java.security.SecureRandom. Это немного медленнее, но работает нормально. Одна вещь, которая может быть проблемой для вас, заключается в том, что она не повторяется. Два экземпляра SecureRandom с одним и тем же семенем не будут давать одинаковый результат. Это по дизайну.
Итак, какие еще существуют варианты? Здесь я подключаю мою собственную библиотеку. Он включает в себя 3 повторяющихся псевдо-RNG, которые быстрее, чем SecureRandom и более случайные, чем java.util.Random. Я их не изобретал, я просто портировал их из исходных версий C. Все они потокобезопасны.
Я реализовал эти RNG, потому что мне нужно что-то лучшее для моего кода эволюционных вычислений. В соответствии с моим первоначальным кратким ответом этот код многопоточен, но он использует только один экземпляр RNG, разделяемый между всеми потоками.