简体   繁体   English

CompletableFuture 在完成时工作缓慢

[英]CompletableFuture working slowly on complete

So I've been running some tests on the CompletableFuture class, and I've stumbled upon some weird behaviour I can't explain.所以我一直在 CompletableFuture 类上运行一些测试,我偶然发现了一些我无法解释的奇怪行为。

Issue问题

I've managed to reduce the issue down to this code snippet我已经设法将问题减少到这个代码片段

        long nanos = System.nanoTime();
        CompletableFuture<UUID> someFuture = CompletableFuture.supplyAsync(()->{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            return UUID.randomUUID();
        });
        someFuture.get(5, TimeUnit.SECONDS);
        System.out.println((System.nanoTime() - nanos) / 1_000_000 + "ms");

I would expect this piece of code to run in around 10 milliseconds.我希望这段代码在大约 10 毫秒内运行。 For some reason, running it ends up taking about 500ms!出于某种原因,运行它最终需要大约 500 毫秒! It should be noted that this only happens in the first execution of the code, and subsequent executions seem to be taking about 10-15 millis (which I am expecting).应该注意的是,这只发生在第一次执行代码时,随后的执行似乎需要大约 10-15 毫秒(这是我期待的)。

My question is, what can I do to get rid of this, either by optimizing it or by not using CompletableFuture.我的问题是,我可以通过优化或不使用 CompletableFuture 来解决这个问题。

What I've tried我试过的

  • I've tried using Runnable's instead of lambdas, no effect.我试过使用 Runnable 而不是 lambdas,没有效果。
  • I've tried overriding the get(long timeout, TimeUnit unit) of the CompletableFuture to poll the result once every millisecond.我尝试覆盖 CompletableFuture 的get(long timeout, TimeUnit unit)以每毫秒轮询一次结果。 This took the latency discrepancy down from 12ms to about 2-3ms, however, it didn't affect the cold boot times.这使延迟差异从 12 毫秒减少到大约 2-3 毫秒,但是,它并没有影响冷启动时间。
  • I've tried starting up new Threads to complete the future, calling ForkJoinPool.commonPool() , tried using Executors.newCachedThreadPool() and Executors.newFixedThreadPool(2);我尝试启动新线程来完成未来,调用ForkJoinPool.commonPool() ,尝试使用Executors.newCachedThreadPool()Executors.newFixedThreadPool(2); . . No effect as far as I can tell.据我所知没有影响。

Background背景

The scenario I'm coming from (to avoid XY problems) is that I am trying to make a simple request-reply model through TCP.我来自的场景(为了避免 XY 问题)是我试图通过 TCP 创建一个简单的请求-回复模型。 I mark each request with a UUID and hold it in a ConcurrentHashMap<UUID, CompletableFuture<String>> and when the packet comes back, I fetch the completable future from the map and complete it with the reply.我用 UUID 标记每个请求并将其保存在ConcurrentHashMap<UUID, CompletableFuture<String>> ,当数据包回来时,我从地图中获取可完成的未来并用回复完成它。 I am doing it like this since replies aren't expected to come in order.我这样做是因为预计不会按顺序回复。 I am experiencing 100ms latency (consistently though, not just the first execution) even though both the server and the client are running on localhost.即使服务器和客户端都在本地主机上运行,​​我也遇到了 100 毫秒的延迟(尽管如此,但不仅仅是第一次执行)。 I've double-checked that the issue is not networking wise, and as far as I can tell, the bottleneck comes down to the CompletableFuture being slow.我已经仔细检查过这个问题不是网络问题,据我所知,瓶颈归结为 CompletableFuture 速度慢。 Is such a Map a good way to approach this problem?这样的Map是解决这个问题的好方法吗? If so, how can I get rid of the latency, if not, how would I go about designing something like this?如果是这样,我怎样才能摆脱延迟,如果没有,我将如何设计这样的东西?

From the docs of UUID.randomUUID :来自UUID.randomUUID的文档

Static factory to retrieve a type 4 (pseudo randomly generated) UUID.用于检索类型 4(伪随机生成)UUID 的静态工厂。 The UUID is generated using a cryptographically strong pseudo random number generator . UUID 是使用加密强的伪随机数生成器生成的

A disadvantage of cryptographic RNGs is that they are slow.加密 RNG 的一个缺点是它们很慢。 Because it is a pseudo-random number generator, the slowness of generating good random numbers only exists when crearing the random number generator.因为它是一个伪随机数生成器,生成好的随机数的缓慢只有在创建随机数生成器时才存在。

If you take a look at the OpenJDK sources of UUID.randomUUID , you can see that the first random number generator is obtained using a simple but effective singleton that is loaded whenever it is called first (when the class Holder is loaded):如果您查看UUID.randomUUID的 OpenJDK 源UUID.randomUUID ,您可以看到第一个随机数生成器是使用一个简单但有效的单例获得的,该单例在首次调用时(加载类Holder )加载:

private static class Holder {
        static final SecureRandom numberGenerator = new SecureRandom();
    } 
SecureRandom ng = Holder.numberGenerator;

As a solution, you can just run UUID.randomUUID() at the start of the program in the background doing the work earlier.作为解决方案,您可以在程序开始时在后台运行UUID.randomUUID()以提前完成工作。

If you don't need cryptographic strong UUIDs, you can create those using a non-cryptographically strong RNG:如果您不需要加密强 UUID,您可以使用非加密强 RNG 创建这些 UUID:

Random rand=new Random();//do this once

//do this whenever you need a new UUID
UUID uuid=new UUID(rand.nextLong(),rand.nextLong());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM