CompletableFuture执行线程的一次研究

来源:互联网 发布:sql数据库服务器搭建 编辑:程序博客网 时间:2024/06/06 13:02

在研究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<AsyncResult<Float>> 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 | startThread-0 || 500.0Thread-1 inner Threadmain|||5000.0main|||| end...

对于

Thread-0 || 500.0

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


但对于

main|||5000.0

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

main | startThread-0 || 500.0Thread-1 inner Threadmain|||| 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<Void> uniAcceptStage(Executor e,                                                   Consumer<? super T> f) {        if (f == null) throw new NullPointerException();        CompletableFuture<Void> d = new CompletableFuture<Void>();        if (e != null || !d.uniAccept(this, f, null)) {            UniAccept<T> c = new UniAccept<T>(e, d, this, f);            push(c);            c.tryFire(SYNC);        }        return d;    }

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


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


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





0 0