使用多线程时SecureRandom流表现异常

3

我正在尝试使用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(); 
编辑:

另一方面,如果我避免使用流(stream)并且只是在每个线程中调用SecureRandom.nextInt(),则如预期那样不会观察到竞争条件。

Thread t1 = new Thread(() -> secureRandom.nextInt());
Thread t2 = new Thread(() -> secureRandom.nextInt());
t1.start();
t2.start(); // code is thread-safe

我想知道迭代器为什么会改变行为?特别是在ints()方法的Javadocs中说明了,"生成一个伪随机int值,就像调用nextInt()方法一样"

P.S.:当然,我可以通过同步获取下一个值的线程来解决这个问题。

1个回答

1

SecureRandom本身是线程安全的,但流不是。整个Streams API都是为单个线程访问而构建的。虽然中间操作可以并行执行,但必须从单个线程调用。

因此,ints()及其迭代器都不是线程安全的。

因此,您可以为每个线程创建一个流。

Thread t1 = new Thread(() -> secureRandom.ints().distinct().iterator().next());
Thread t2 = new Thread(() -> secureRandom.ints().distinct().iterator().next());
t1.start();
t2.start();

谢谢回答。了解关于流如何与线程安全一起工作,特别是在iterator()方面会很有趣。然而,提出的解决方案并不是非常适合,因为意图是确保不同的值(即使这很不可能重复生成任何值)。 - M A
@MAnouti 我已经解释了流的线程安全性。你还想知道什么?为了确保多个线程之间的不同值,你需要实现你自己的解决方案(也许可以使用并发哈希集)。 - spongebob
这里我特别关注iterator()操作,它似乎很特殊,因为它不像其他终端操作一样评估整个流。ints()方法的文档说明“伪随机int值就像调用nextInt()方法的结果一样。”现在,如果我只是使用nextInt(),就不会发生竞争条件。所以我想知道为什么iterator()的行为会有所不同(我已经更新了问题以澄清)。 - M A
如果有关于流通常不是线程安全的官方文档或讨论的参考资料,我可以接受这个答案。尽管这仍然感觉像是破坏了SecureRandom的线程安全保证。 - M A
1
@MAnouti 我认为线程安全的类可以有不是线程安全的衍生类。Iterator 的契约并不要求它是线程安全的。流的迭代器会表现出不同的行为,因为它是从流中生成的,而流不是线程安全的。 - spongebob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接