Netty_Future&Promise

一、Future

1.1 简要介绍

JDK中Future的常用实现, 可用于解决异步场景下的等待/通知问题。在FutureTask中, 调用者可以无阻赛方式调用isDone检查完成状态, 或者阻塞在get()方法中直到Task运行结束或者抛出异常。

1.2 内部结构

  1. callable
    可能是runnable+result或者callable。前者运行完成后返回result, 毕竟runnable本身不提供返回值。FutureTask内部通过RunnableAdapter将Runnable适配成Callable。
  2. state
    内部状态, 状态流转如下图所示
before outcome=v
after outcome=v
before mark exception
after marked exception
cancel(false)
cancel(true)
runner.interrupt
NEW
COMPLETING
NORMAL
EXCEPTIONAL
CANCELLED
INTERRUPTING
INTERRUPTED
  1. runner
    运行当前Task对应的Thread

  2. waiters
    类型为WaiterNode, Treiber stack的一种实现, 用于保存等待获取结果的线程。关于该数据结构的由来, 请参考(https://dominoweb.draco.res.ibm.com/reports/rj5118.pdf)

1.3 基本运转逻辑

  1. 状态图中, 自顶向下有三类, 开始状态, 中间状态和最终状态;
  2. Task处于中间状态时, 所有调用get的线程会加入到waiters, 成为等待队列的一员;
  3. Task处于最终状态时, 所有waiters成员线程会被逐一唤醒(具体通过LockSupport.unpark(线程对象))实现;

二、Netty的扩展

2.1 FutureTask的弊端

在FutureTask中, 仅支持通过isDone验证是否结束, 最终还是得靠阻塞的get获取结果。在高性能计算的场景下, 阻塞是不能接受的, 因为阻塞意味着操作系统重新调度线程, 有额外的上下文切换开销。

2.2 Netty的改进

2.2.1 Netty Future

  1. 支持通过addListener增加GenericFutureListener, 当任务完成时执行该listener;
  2. 在网络场景下, cancel必要性不强, Future要么正常执行得到目标结果要么失败, 因此提供isCancelable方法判断。
  3. JDK Future其运行一般在外部的ThreadPool, 而Netty的逻辑一般在EventExecutor, 因此Netty中的CompleteFuture构造函数要求关联EventExecutor.

2.2.2 Netty Promise

  1. Netty Future的子类;
  2. Netty Future是只读的, 但是Promise是一次性可写的, 可以在任务未运行结束的情况下, 设置Promise结果、取消或者异常结束;
  3. 与FutureTask默认实现不同, DefaultPromise基于synchronized的wait和notifyAll实现, set/get之间的协调;
  4. 对Listener的通知需要在EventLoop环境下完成;

三、使用场景

通过前面的了解, 显然是应用在多线程场景下或者并发场景下:

  1. 从解决问题角度, 其思路非常经典, 调用端要么主动阻塞/等待, 要么注册回调等通知;
  2. 从线程协调的角度, 基于synchornized做协调, 线程之间隐含一个条件就是需要基于同一把锁。协调者(锁)与被协调者(线程)产生了耦合–因为逻辑中必须明确持有哪一把锁对象来协调。而通过Future/Promise则将锁独立出来, 所有参与对Future/Promise操作的线程之间自动进行协调, 线程对锁是无感的, 算是一种协调反转(想想Spring中的控制反转);
  3. 从组合性的角度, 例如A、B线程关联Future 1, B、C线程关联Future 2, 如果能够做到Future 1完成后继续完成Future 2自动触发就意味着Future 1和Future 2可组合, 就意味着可以像搭积木一样轻松搞定复杂逻辑, 并保证线程安全。这就是CompletableFuture。这玩意儿带来的直观改善大概是这样的。

基于纯Future的逻辑组织

Future a = funA();
Object aResult = a.get();

Future b = funB(aResult);
Object bResult = b.get();

return bResult;

基于CompletableFuture的逻辑组织

CompletableFuture a = funA();
CompletableFuture b = a.thenApply((aResult) -> {
    // your logic
    return bResult;
});

Object bResult = b.get();
return bResult;

前者数值传递需要当前线程参与, 后者则是拿结果即可, 差别不仅是代码书写上, 也体现在资源消耗上, 相当于原本两个负责运行任务的线程A、B可以直接打交道(A->B), 结果还得把当前线程加进去当中介(A -> 当前线程 -> B), 线程上下文切换次数直接增加1倍。

四、 小结

本文聊了下Future使用场景和基本实现, 最后进一步探讨代码组织和资源消耗上的改善。

你可能感兴趣的:(Netty网络应用,java,Netty,网络)