【问题标题】:Java 8 CompletedFuture and ThreadLocalRandomJava 8 CompletedFuture 和 ThreadLocalRandom
【发布时间】:2015-03-08 07:30:47
【问题描述】:

我正在练习 Cay S. Horstmann 所著的“真正不耐烦的 Java SE 8”一书。

写一个方法

public static <T> CompletableFuture<T> repeat(
         Supplier<T> action, Predicate<T> until)

异步重复操作,直到它产生一个值 被until 函数接受,它也应该运行 异步。使用读取 a 的函数进行测试 java.net.PasswordAuthentication 来自控制台,还有一个函数 通过睡眠一秒钟然后模拟有效性检查 检查密码是否“秘密”。

我想出了以下代码,但随机密码生成策略似乎让我失望了。所有线程不断选择相同的密码,这看起来很奇怪。

public static <T> CompletableFuture<T> repeat(final Supplier<T> action, final Predicate<T> until) {
    final CompletableFuture<T> futureAction = supplyAsync(action);
    final boolean isMatchFound = futureAction.thenApplyAsync(until::test).join();

    final T suppliedValue = getSuppliedValue(futureAction);

    if (isMatchFound) {
        LOGGER.info("Got a match for value {}.", suppliedValue);

        return futureAction;
    }

    return repeat(() -> suppliedValue, until);
}

private static <T> T getSuppliedValue(final CompletableFuture<T> futureAction) {
    try {
        return futureAction.get();
    } catch (InterruptedException | ExecutionException e) {
        LOGGER.error(e.getMessage());
    }

    return null;
}

测试用例:

@Test
public void testRepeat() {
    Supplier<PasswordAuthentication> action = () -> {
        final String[] passwordVault = new String[] { "password", "secret",
            "secretPassword" };

        final int len = passwordVault.length;

        return new PasswordAuthentication("mickeyMouse",
            passwordVault[ThreadLocalRandom.current().nextInt(len)].toCharArray());
    };
    @SuppressWarnings("static-access")
    Predicate<PasswordAuthentication> until = passwordAuth -> {
        try {
            currentThread().sleep(1000);
        } catch (InterruptedException e) {
            fail(e.getMessage());
        }

        final String password = String.valueOf(passwordAuth.getPassword());

        LOGGER.info("Received password: {}.", password);

        return password.equals("secret");
    };

    repeat(action, until);
}

运行一次,看看选择相同的密码有多奇怪:

2015-01-09 15:41:33.350 [线程 1] [信息] n.a.j.j.c.PracticeQuestionsCh6Test - 收到密码: 秘密密码。 2015-01-09 15:41:34.371 [线程 3] [信息] n.a.j.j.c.PracticeQuestionsCh6Test - 收到密码: 秘密密码。 2015-01-09 15:41:35.442 [线程 5] [信息] n.a.j.j.c.PracticeQuestionsCh6Test - 收到密码: 秘密密码。 2015-01-09 15:41:36.443 [线程 7] [信息] n.a.j.j.c.PracticeQuestionsCh6Test - 收到密码: 秘密密码。 2015-01-09 15:41:37.451 [线程 9] [信息] n.a.j.j.c.PracticeQuestionsCh6Test - 收到密码: 密码。

【问题讨论】:

    标签: multithreading concurrency java-8 thread-local completable-future


    【解决方案1】:

    我发现了错误。我创建的供应商每次都返回相同的值。以下是更正后的代码。但是有一个问题:为什么编译器会强制转换?

    public static <T> CompletableFuture<T> repeat(final Supplier<T> action,
            final Predicate<T> until) {
        final CompletableFuture<T> futureAction = supplyAsync(action);
    
        @SuppressWarnings("unchecked")
        CompletableFuture<T> future = (CompletableFuture<T>) futureAction
            .thenApplyAsync(until::test).thenApply(
                isMatchFound -> isMatchFound ? futureAction : repeat(action, until));
    
        future.join();
    
        return future;
    
    }
    

    【讨论】:

    • 因为正如您所写的,代码创建了CompletableFuture&lt;CompletableFuture&lt;T&gt;&gt; 而不是CompletableFuture&lt;T&gt;。链中的最后一个调用应该是thenComposeAsync 而不是thenApply。但是您的代码仍然是异步的,但不是并行的,因为每个步骤都是在 上一个阶段完成之后触发的。因此,如果你想要并行执行,你应该使用Stream.generate(action).parallel().filter(until).findFirst()...
    • @Holger 是的,并非每一步都是异步的,问题陈述也没有要求这样做。它只要求untilaction 异步,我打算在until 上使用thenApplyAsync 来实现。为什么你认为thenComposeAsync 是比thenApply 更好的选择作为管道的最后一步?我不需要最后一步是异步的,所以即使我使用 compose,我也会选择 thenCompose,但我很好奇你为什么会选择 compose 而不是 apply。
    • @Holger 如果我使用 compose,那么返回类型将是 CompletableFuture&lt;T&gt; 而不是 &lt;CompletableFuture&lt;CompletableFuture&lt;T&gt;&gt;。这也解释了演员阵容。但是,阅读 Javadoc,似乎 compose 接受 previous stage 作为参数,而不是前一个 stage 的 result。然而,这似乎并没有在实践中发生 - 它似乎正在接收布尔结果而不是CompletableFuture&lt;Boolean&gt;“返回一个新的 CompletionStage,当此阶段正常完成时,将使用此阶段作为所提供函数的参数执行。”
    【解决方案2】:

    我认为你正在走一条不必要的复杂道路。由于供应-测试-供应-测试-供应-测试序列是连续的,初始supplyAsync 就是您所需要的。没有理由在异步中进行异步。

    这是一个简单的实现:

    public static <T> CompletableFuture<T> repeat(final Supplier<T> action,
            final Predicate<T> until) {
    
        return CompletableFuture.supplyAsync(() -> 
            Stream.generate(action)
                    .filter(until)
                    .findFirst()
                    .get()
        );
    }
    

    【讨论】:

    • 我认为这不符合until函数也应该异步运行的要求。请参阅我上面帖子中的问题陈述。
    • @Abhijit Sarkar: Stream.parallel()...
    猜你喜欢
    • 2017-02-11
    • 1970-01-01
    • 2014-06-17
    • 1970-01-01
    • 2017-05-25
    • 2013-11-01
    • 2018-01-08
    • 2017-09-18
    • 1970-01-01
    相关资源
    最近更新 更多