Есть ли причина писать `new Random()` с Java 8?

По какой-то причине я полагал, что java.util.Random является поточно-опасным, a-la HashMap или BitSet, а Math.random() реализуется либо как обертывающий доступ к Random с блоком synchronized или ThreadLocalRandom.current().nextDouble().

На самом деле оказывается, что java.util.Random является потокобезопасным (через атомику). Следовательно, вынос: даже если мне нужен какой-то случайный ввод в одном потоке, имеет смысл использовать ThreadLocalRandom, потому что внутри не существует атомарного чтения и записи, скомпилированного как заблокированные инструкции и испускающие барьеры памяти.

Более того, поскольку Java 8, ThreadLocalRandom по существу является одноэлементным, его состояние сохраняется в некоторых полях класса java.lang.Thread. Поэтому метод ThreadLocalRandom.current() не является доступом к ThreadLocalMap, а только прочитанным статическим полем, т.е. е. очень дешево.

У меня есть два вопроса:

  • С точки зрения компьютерных наук, выход нескольких линейных конгруэнтных случайных генераторов (инициализированных способом ThreadLocalRandom) является таким же "случайным", как выход одиночного линейного конгруэнтного случайного генератора (java.util.Random экземпляр)?

  • Если ответ на первый вопрос есть Да, есть ли причина написать конструкцию new Random() (без семени) вместо ThreadLocalRandom.current(), когда-либо?

Update. Я предположил, что вызовы типа ThreadLocalRandom.current().ints().parallel().collect(...) могут быть некорректными, потому что случайное состояние генератора Thread не может быть инициализировано в ForkJoinPool рабочих потоках, но появляется, что ThreadLocalRandom переопределяет методы ints(), longs() и doubles(), что делает выше правильной конструкции.

Ответы

Ответ 1

1...

Это зависит от реализации, но для Java это будет same не так плохо, потому что Java имеет статический уникальный семенной атомный длинный, который каждый раз манипулирует, создается Random. Однако я не удивлюсь другим языкам или реализациям, это не так, и они могут просто использовать системное время (Java также использует системное время, но использует уникальное семя в комбинации). То есть на некоторых системах вы можете получить одно и то же семя для нескольких потоков.

После дальнейшего изучения и некоторого фактического тестирования (хотя и с хрупким тестированием), возможно, я ошибался до, поскольку на самом деле хуже использовать многие (я говорю 100k) генераторы случайных чисел в в то же время (хотя это разные экземпляры). Я не совсем уверен, что его столкновения с семенем или просто факт, что фактическое глобальное приращение семян станет предсказуемым. Конечно, это может быть просто мой тестовый жгут или методология.

Согласно википедии:

Генераторы случайных чисел, особенно для параллельных компьютеров, не должны доверять. [12] Настоятельно рекомендуется проверить результаты моделирования с более чем одним RNG, чтобы проверить, не введен ли предвзятость. Среди рекомендуемых генераторов для использования на параллельном компьютере включают объединенные линейные конгруэнтные генераторы, использующие разделение последовательностей и запаздывающие генераторы Фибоначчи с использованием независимых последовательностей.

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

Это, конечно, основано на псевдо-случайном.

Физическая хаотичность или безопасный случайный генератор, основанный на фактической энтропии, может привести к различиям (то есть больше/меньше энтропии), но я не эксперт, и у меня нет доступа к одному.

2...

Я не могу придумать конкретный вариант использования, но, возможно, вы используете ExecutorService, который постоянно создает и удаляет потоки (предположим, что у них нет контроля над этим), но не так много сразу (т.е. max 2 параллельные потоки). Вы могли бы найти, что ThreadLocalRandom будет стоить дороже, а не создавать единый общий Random.

Еще одна причина и, вероятно, лучшая причина, связанная с вашими комментариями, заключается в том, что вы можете захотеть reset семени для всех процессов. Если у вас есть игра, в которой используются потоки (не многие из них делают, но позволяют делать вид), вы можете захотеть глобальное reset семя для целей тестирования, которое намного проще с AtomicReference to Random, чем попытка передать сообщение всем работающим потокам.

Другая причина, по которой вы, возможно, не захотите использовать ThreadLocalRandom, - это причины платформы. Некоторые платформы имеют особые требования к созданию потоков и, следовательно, созданию потоков. Таким образом, для решения проблемы "у вас есть большая проблема, чем рандомы", зайдите в Google Apps, где:

Java-приложение может создавать новый поток, но есть некоторые ограничения на то, как это сделать. Эти потоки не могут "пережить" запрос, который их создает. (На бэкэнд-сервере приложение может порождать фоновый поток, поток, который может "пережить" запрос, который его создает.)

И чтобы ответить на ваш дополнительный комментарий, почему вы используете ExecutorService, который не может повторно использовать потоки:

или использовать объект factory, возвращенный com.google.appengine.api.ThreadManager.currentRequestThreadFactory() с помощью ExecutorService (например, вызвать Executors.newCachedThreadPool(factory)).

т.е. ThreadPool, который не обязательно повторно использует потоки.