#java #multithreading #stream #thread-safety #secure-random
#java #многопоточность #поток #безопасность потоков #безопасный-случайный
Вопрос:
Я пытаюсь генерировать случайные значения, используя SecureRandom
, в частности, его поддержку потоков. В идеале значения должны генерироваться на постоянной основе, поэтому поток может быть бесконечным:
SecureRandom secureRandom = new SecureRandom();
Iterator<Integer> idIterator = secureRandom.ints().distinct().iterator();
В документации указано, что « SecureRandom
объекты безопасны для использования несколькими параллельными потоками». Однако, когда несколько потоков извлекают следующее значение из итератора, я получаю ошибку (по крайней мере) в одном из потоков, которая, по-видимому, вызвана состоянием гонки:
Thread t1 = new Thread(() -> idIterator.next());
Thread t2 = new Thread(() -> idIterator.next());
t1.start();
t2.start();
Exception in thread "Thread-1" java.lang.IllegalStateException: source already consumed or closed
at java.base/java.util.stream.AbstractPipeline.sourceSpliterator(AbstractPipeline.java:409)
at java.base/java.util.stream.AbstractPipeline.lambda$spliterator$0(AbstractPipeline.java:367)
at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.init(StreamSpliterators.java:142)
at java.base/java.util.stream.StreamSpliterators$AbstractWrappingSpliterator.doAdvance(StreamSpliterators.java:157)
at java.base/java.util.stream.StreamSpliterators$IntWrappingSpliterator.tryAdvance(StreamSpliterators.java:358)
at java.base/java.util.Spliterators$2Adapter.hasNext(Spliterators.java:726)
at java.base/java.util.Spliterators$2Adapter.nextInt(Spliterators.java:732)
at java.base/java.util.PrimitiveIterator$OfInt.next(PrimitiveIterator.java:128)
at java.base/java.util.PrimitiveIterator$OfInt.next(PrimitiveIterator.java:86)
at example.Example.foo(Example.java:39)
Когда я запускаю код несколько раз, я иногда получаю исключение другого типа (исключение NullPointerException).
Поведение будет таким же, если я ограничу поток и удалю distinct()
операцию:
secureRandom.ints().limit(100).iterator();
Редактировать:
С другой стороны, если я избегаю использования потока и просто вызываю SecureRandom.nextInt()
из каждого потока, условие гонки не наблюдается, как ожидалось.
Thread t1 = new Thread(() -> secureRandom.nextInt());
Thread t2 = new Thread(() -> secureRandom.nextInt());
t1.start();
t2.start(); // code is thread-safe
Мне интересно, почему итератор меняет поведение? Особенно то, что в Javadocs ints()
метода указано, что «псевдослучайное значение int генерируется так, как будто это результат вызова метода nextInt()
«.
PS: Конечно, я могу решить эту проблему, но синхронизируя потоки, получающие следующее значение.
Ответ №1:
Хотя SecureRandom
сам по себе потокобезопасен, потоки — нет. Весь API Streams был создан для доступа к одному потоку. Хотя промежуточные операции могут выполняться параллельно, они должны вызываться из одного потока.
Поэтому ни ints()
итератор, ни его итератор не являются потокобезопасными.
Итак, что вы можете сделать, это создать один поток на поток.
Thread t1 = new Thread(() -> secureRandom.ints().distinct().iterator().next());
Thread t2 = new Thread(() -> secureRandom.ints().distinct().iterator().next());
t1.start();
t2.start();
Комментарии:
1. Спасибо за ответ. Было бы интересно узнать больше о том, как потокобезопасность работает с потоками, в частности с
iterator()
. Предлагаемое решение, однако, не очень подходит, поскольку цель состоит в том, чтобы обеспечить различные значения (т. Е. Никакие значения не генерируются повторно, даже если это маловероятно).2. @MAnouti Я объяснил потокобезопасность потоков. Что еще вы хотели бы знать? Чтобы обеспечить различные значения в нескольких потоках, вам нужно будет реализовать свое решение (возможно, использовать параллельный набор хэшей).
3. Здесь меня особенно интересует
iterator()
операция, которая кажется особенной тем, что она не оценивает весь поток, в отличие от других операций терминала. В документахints()
метода указано «псевдослучайное значение int выглядит так, как будто оно является результатом вызова методаnextInt()
» . Теперь, если я просто используюnextInt()
, условие гонки не возникает. Так что просто интересно, почемуiterator()
будет вести себя по-другому (я обновил вопрос, чтобы уточнить).4. Я могу принять ответ, если есть ссылка на тот факт, что потоки просто не являются потокобезопасными в целом (например, официальные документы или обсуждение). Хотя все равно будет казаться, что это нарушает гарантию потокобезопасности
SecureRandom
.5. @MAnouti Я думаю, что потокобезопасные классы могут иметь выделенные классы, которые не являются потокобезопасными. Контракт
Iterator
не требует, чтобы он был потокобезопасным. Итератор потока будет вести себя по-другому, потому что он генерируется из потока, а потоки не являются потокобезопасными.