CompletableFuture执行线程的一次研究

在研究vertx线程模型的时候我开始注意到在vertx内部提供给开发者的多数异步api中用到了CompletableFuture或者Promise或者Future。以前也用过CompletableFuture和Futrue等,但当时没有想过它执行时的线程情况,于是写了个测试类用于了解它:

public class VertxTest {
  Logger logger = LoggerFactory.getLogger(VertxTest.class);

  @Test
  public void test() {
    System.out.println(Thread.currentThread().getName() + " | start");
    testAsync(1000, result -> {
      System.out.println(Thread.currentThread().getName() + " || " + result);
    });
    CompletableFuture future1 = testAsync(10000);
    future1.thenAccept(result -> {
      System.out.println(Thread.currentThread().getName() + "|||" + result);
    });
    System.out.println(Thread.currentThread().getName() + "|||| end..." );

    while (true) {}
  }

  private void testAsync(int max, Handler> handler) {
    Thread thread = new Thread(() -> {
      float result = 0;
      for (int i = 0; i < max; i++) {
        result += 0.5;
      }
      handler.handle(Future.succeededFuture(result));
    });
    thread.start();
  }

  private CompletableFuture testAsync(int max) {
    CompletableFuture future = new CompletableFuture();
    new Thread(() -> {
      System.out.println(Thread.currentThread().getName() + " inner Thread");
      float result = 0;
      for (int i = 0; i < max; i++) {
        result += 0.5;
      }
      future.complete(result);
    }).start();
    return future;
  }
}

在我电脑上的执行结果:

main | start
Thread-0 || 500.0
Thread-1 inner Thread
main|||5000.0
main|||| end...

对于

Thread-0 || 500.0

Thread-1 inner Thread
很好理解,在我new的那个线程中执行。


但对于

main|||5000.0

会有个问题:当把参数从10000调到100000000时,我电脑上的结果是:

main | start
Thread-0 || 500.0
Thread-1 inner Thread
main|||| end...
Thread-1|||8388608.0

请注意,对于

future1.thenAccept(result -> {
      System.out.println(Thread.currentThread().getName() + "|||" + result);
    });

中的那句打印,也就是传进去的这个Handler实例的handle(String param)调用,是在Thread-1中。而上面参数是10000时这个调用在main线程里。


为什么会有这个不同呢,或者说什么机制在调用future1.thenAccept(Handler handler)时,决定handler.handler(...)的调用要在哪个线程?


查看thenAccept的源代码,跟踪进去看到下面的片段:

private CompletableFuture uniAcceptStage(Executor e,
                                                   Consumer f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture d = new CompletableFuture();
        if (e != null || !d.uniAccept(this, f, null)) {
            UniAccept c = new UniAccept(e, d, this, f);
            push(c);
            c.tryFire(SYNC);
        }
        return d;
    }

这里的逻辑就是,如果调用thenAccept方法时,结果已经得到了,则直接返回,由当前调用thenAccept的线程调用handler.handle(...),否则会将传进来的handler包装成一个UniAccept对象,放入到一个stack里,等待我new的那个线程执行完之后回调handler.handle(...),也就不在main线程中了。


这就解释了为什么我测试例子中当参数调大之后不是在main中执行那个打印,而是在一个新的线程中的缘故。CompletableFuture这么做的好处在于,对时间短的处理,直接返回结果,而对于“询问”结果时还没有计算出来的,则可以非阻塞的继续往下执行等待结果执行完了回调。


对于多线程编程,清楚你写的代码块在哪个线程上下文环境中执行是很重要的,所以了解了CompletableFuture的thenAccept()方法的这个机制,将会大有好处。CompletableFuture还提供了很多有用的方法,其机制也可以和thenAccept机制比较思考。





你可能感兴趣的:(Java异步编程,java)