IntStream из Random и Random concurrency

Можно ли использовать один и тот же экземпляр Random для генерации потока (или параллельного потока) и влиять на этот поток в одной из его частей?

Рассмотрим приведенный ниже код. Тот же gen используется для создания параллельной IntStream и генерации случайного пространства каждые несколько символов. Он запускается и завершается успешно, без исключения.

Но безопасен ли этот код? Похоже, что это так, потому что не существует недопустимых (вне диапазона) значений символов. Я думаю, что я должен повредить внутренние данные Random, так как его методы не отмечены как synchronized, но, по-видимому, это не так. Почему?

public class RandomGenTest {

    Random gen = new Random();

    String getRandomText(int len, double spaceProb) {
        return gen.ints(len, 'a', 'z'+1)
                    .map(i-> gen.nextDouble()<spaceProb?' ':i)
                    .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
    }

    @Test
    public void test() {
        for (int a=10000; a<10000000; a*=2) {
            String text = getRandomText(a, .2);
            Assert.assertTrue(text.chars().allMatch(c -> (c>='a' && c<='z') || c==' '));
        }
    }

}

Ответ 1

Random Javadoc говорит:

Экземпляры java.util.Random являются потокобезопасными. Однако одновременное использование одного и того же экземпляра java.util.Random через потоки может столкнуться с конкуренцией и, как следствие, низкой производительностью. Вместо этого используйте ThreadLocalRandom в многопоточных конструкциях.

Random - это поточно-безопасный объект в силу AtomicLong, который сохраняет текущее семя, поэтому его использование отменяет большую часть параллельного ускорения, которое является точкой вашего упражнения.

Вместо этого используйте ThreadLocalRandom.getCurrent() и избегайте, по крайней мере, проблемы с конкуренцией (хотя путем введения накладных расходов на поиск ThreadLocal). Также используйте SplittableRandom для извлечения внешнего потока случайных чисел. Эта реализация позволяет произвольный доступ к элементам потока, что является ключом к параллелизуемости goood.

import static java.util.concurrent.ThreadLocalRandom.current;

String getRandomText(int len, double spaceProb) {
    return new SplittableRandom().ints(len, 'a', 'z'+1).parallel()
      .map(i -> current().nextDouble()<spaceProb ? ' ' : i)
      .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();