【问题标题】:SecureRandom stream weird behavior with multiple threads具有多个线程的 SecureRandom 流奇怪行为
【发布时间】:2020-12-02 11:29:52
【问题描述】:

我正在尝试使用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

我想知道为什么迭代器会改变行为?特别是 ints() 方法的 Javadocs 声明 “生成一个伪随机 int 值,就好像它是调用方法 nextInt() 的结果一样”

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

【问题讨论】:

    标签: java multithreading stream thread-safety secure-random


    【解决方案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()。然而,建议的解决方案不是很合适,因为其目的是确保不同的值(即即使这极不可能,也不会重复生成任何值)。
    • @MAnouti 我解释了流的线程安全性。你还想知道什么?为了确保跨多个线程的不同值,您需要实现您的解决方案(可能使用并发哈希集)。
    • 这里我对iterator() 操作特别感兴趣,它看起来很特别,与其他终端操作不同,它不会评估整个流。 ints() 方法的文档状态“伪随机 int 值就好像它是调用方法 nextInt() 的结果。”现在如果我简单地使用nextInt(),就不会出现竞争条件。所以只是想知道为什么iterator() 的行为会有所不同(我已经更新了问题以澄清)。
    • 如果提到流通常不是线程安全的事实(如官方文档或讨论),我可以接受答案。虽然它仍然感觉它打破了SecureRandom 的线程安全保证。
    • @MAnouti 我认为线程安全类可以有非线程安全的衍生类。 Iterator 的合约不要求它是线程安全的。流的迭代器会表现不同,因为它是从流中生成的,并且流不是线程安全的。
    猜你喜欢
    • 1970-01-01
    • 2015-01-07
    • 2013-02-15
    • 2013-09-23
    • 2017-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-06
    相关资源
    最近更新 更多