Future用于获取异步操作的结果,而Promise则比较抽象,无法直接猜测出其功能。

Future

Future最早来源于JDK的java.util.concurrent.Future,它用于代表异步操作的结果。

可以通过get方法获取操作结果,如果操作尚未完成,则会同步阻塞当前调用的线程;如果不允许阻塞太长时间或者无限期阻塞,可以通过带超时时间的get方法获取结果;如果到达超时时间操作仍然没有完成,则抛出TimeoutException。通过isDone()方法可以判断当前的异步操作是否完成,如果完成,无论成功与否,都返回true,否则返回false。通过cancel可以尝试取消异步操作,它的结果是未知的,如果操作已经完成,或者发生其他未知的原因拒绝取消,取消操作将会失败。

由于Netty的Future都是与异步I/O操作相关的,因此,命名为ChannelFuture,代表它与Channel操作相关。

在Netty中,所有的I/O操作都是异步的,这意味着任何I/O调用都会立即返回,而不是像传统BIO那样同步等待操作完成。异步操作会带来一个问题:调用者如何获取异步操作的结果?ChannelFuture就是为了解决这个问题而专门设计的。

ChannelFuture有两种状态:uncompleted和completed。当开始一个I/O操作时,一个新的ChannelFuture被创建,此时它处于uncompleted状态——非失败、非成功、非取消,因为I/O操作此时还没有完成。一旦I/O操作完成,ChannelFuture将会被设置成completed,它的结果有如下三种可能。

  1. 操作成功;
  2. 操作失败;
  3. 操作被取消。

ChannelFuture提供了一系列新的API,用于获取操作结果、添加事件监听器、取消I/O操作、同步等待等。

Netty强烈建议直接通过添加监听器的方式获取I/O操作结果,或者进行后续的相关操作。

ChannelFuture可以同时增加一个或者多个GenericFutureListener,也可以通过remove方法删除GenericFutureListener。

当I/O操作完成之后,I/O线程会回调ChannelFuture中GenericFutureListener的operationComplete方法,并把ChannelFuture对象当作方法的入参。如果用户需要做上下文相关的操作,需要将上下文信息保存到对应的ChannelFuture中。

推荐通过GenericFutureListener代替ChannelFuture的get等方法的原因是:当我们进行异步I/O操作时,完成的时间是无法预测的,如果不设置超时时间,它会导致调用线程长时间被阻塞,甚至挂死。而设置超时时间,时间又无法精确预测。利用异步通知机制回调GenericFutureListener是最佳的解决方案,它的性能最优。

ps:不要在ChannelHandler中调用ChannelFuture的await()方法,这会导致死锁。原因是发起I/O操作之后,由I/O线程负责异步通知发起I/O操作的用户线程,如果I/O线程和用户线程是同一个线程,就会导致I/O线程等待自己通知操作完成,这就导致了死锁,这跟经典的两个线程互等待死锁不同,属于自己把自己挂死。

异步I/O操作有两类超时:一个是TCP层面的I/O超时,另一个是业务逻辑层面的操作超时。两者没有必然的联系,但是通常情况下业务逻辑超时时间应该大于I/O超时时间,它们两者是包含的关系。

ps:ChannelFuture超时并不代表I/O超时,这意味着ChannelFuture超时后,如果没有关闭连接资源,随后连接依旧可能会成功,这会导致严重的问题。所以通常情况下,必须要考虑究竟是设置I/O超时还是ChannelFuture超时。

ChannelFuture源码分析

AbstractFuture实现Future接口,它不允许I/O操作被取消。

    @Override
public V get() throws InterruptedException, ExecutionException {
//调用await()方法进行无限期阻塞,当I/O操作完成后会被notify()。
await();
Throwable cause = cause();
//程序继续向下执行,检查I/O操作是否发生了异常
if (cause == null) {
//如果没有异常,则通过getNow()方法获取结果并返回。
return getNow();
}
//否则,将异常堆栈进行包装,抛出ExecutionException。
throw new ExecutionException(cause);
} @Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
//调用await(long timeout, TimeUnit unit)方法即可
if (await(timeout, unit)) {
Throwable cause = cause();
//如果没有超时,则依次判断是否发生了I/O异常等情况
if (cause == null) {
//操作与无参数的get方法相同。
return getNow();
}
throw new ExecutionException(cause);
}
//如果超时,则抛出TimeoutException。
throw new TimeoutException();
}

Promise

Promise是可写的Future,Future自身并没有写操作相关的接口,Netty通过Promise对Future进行扩展,用于设置I/O操作的结果。

Promise相关的写操作接口定义如图:

Netty发起I/O操作的时候,会创建一个新的Promise对象,例如调用ChannelHandlerContext的write(Object object)方法时,会创建一个新的ChannelPromise。

    @Override
public ChannelFuture write(Object msg) {
return write(msg, newPromise());
}
@Override
public ChannelFuture write(Object msg, ChannelPromise promise) {
DefaultChannelHandlerContext next = findContextOutbound(MASK_WRITE);
next.invoker.invokeWrite(next, msg, promise);
return promise;
}
@Override
public ChannelPromise newPromise() {
return new DefaultChannelPromise(channel(), executor());
}

Promise源码分析

分析一个它的实现子类的源码DefaultPromise 。

setSuccess方法的实现

    @Override
public Promise<V> setSuccess(V result) {
//调用setSuccess0方法并对其操作结果进行判断,如果操作成功,则调用notifyListeners方法通知listener。
if (setSuccess0(result)) {
notifyListeners();
return this;
}
throw new IllegalStateException("complete already: " + this);
}
private boolean setSuccess0(V result) {
//首先判断当前Promise的操作结果是否已经被设置,如果已经被设置,则不允许重复设置,返回设置失败。
if (isDone()) {
return false;
}
//由于可能存在I/O线程和用户线程同时操作Promise,所以设置操作结果的时候需要加锁保护,防止并发操作。
synchronized (this) {
//对操作结果是否被设置进行二次判断(为了提升并发性能的二次判断),如果已经被设置,则返回操作失败。
if (isDone()) {
return false;
}
//对操作结果result进行判断,如果为空,说明仅仅需要notify在等待的业务线程,不包含具体的业务逻辑对象。
//因此,将result设置为系统默认的SUCCESS。
if (result == null) {
this.result = SUCCESS;
} else {
//如果操作结果非空,将结果设置为result。
this.result = result;
}
//如果有正在等待异步I/O操作完成的用户线程或者其他系统线程
if (hasWaiters()) {
//调用notifyAll方法唤醒所有正在等待的线程。注意,notifyAll和wait方法都必须在同步块内使用。
notifyAll();
}
}
return true;
}

await方法的实现

    @Override
public Promise<V> await() throws InterruptedException {
//如果当前的Promise已经被设置,则直接返回。
if (isDone()) {
return this;
}
//如果线程已经被中断,则抛出中断异常。
if (Thread.interrupted()) {
throw new InterruptedException(toString());
}
//通过同步关键字锁定当前Promise对象
synchronized (this) {
//使用循环判断对isDone结果进行判断,进行循环判断的原因是防止线程被意外唤醒导致的功能异常。
while (!isDone()) {
checkDeadLock();
incWaiters();
try {
wait();
} finally {
decWaiters();
}
}
}
return this;
}

由于在I/O线程中调用Promise的await或者sync方法会导致死锁,所以在循环体中需要对死锁进行保护性校验,防止I/O线程被挂死,最后调用java.lang.Object.wait()方法进行无限期等待,直到I/O线程调用setSuccess方法、trySuccess方法、setFailure或者tryFailure方法。

Future和Promise的更多相关文章

  1. Future与Promise

    https://code.csdn.NET/DOC_Scala/chinese_scala_offical_document/file/Futures-and-Promises-cn.md#ancho ...

  2. C++之future和promise

    future和promise的作用是在不同线程之间传递数据.使用指针也可以完成数据的传递,但是指针非常危险,因为互斥量不能阻止指针的访问:而且指针的方式传递的数据是固定的,如果更改数据类型,那么还需要 ...

  3. Netty 中的异步编程 Future 和 Promise

    Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ...

  4. Scala教程之:Future和Promise

    文章目录 定义返回Future的方法 阻塞方式获取Future的值 非阻塞方式获取Future的值 Future链 flatmap VS map Future.sequence() VS Future ...

  5. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

  6. C++11多线程のfuture,promise,package_task

    一.c++11中可以在调用进程中获取被调进程中的结果,具体用法如下 // threadTest.cpp: 定义控制台应用程序的入口点. // #include "stdafx.h" ...

  7. C++并发编程之std::async(), std::future, std::promise, std::packaged_task

    c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...

  8. [netty4][netty-common]Future与Promise分析

    接口与类结构体系 -- [I]java.util.concurrent.Future<V> ---- [I]io.netty.util.concurrent.Future<V> ...

  9. C++多线程の线程通信future,promise,async

随机推荐

  1. 把代码搬到Git Hub 吧(一)

    作为码农的我们,应该都是知道Git Hub,因为git几乎是码农必备的技能啊,所以就不多介绍Git Hub了,直入主题,这篇博客主要讲解Git Hub网页端和客户端的操作. 网页端: 首页第一步自然是 ...

  2. Windows 安装JRuby 生成 war 到 tomcat 运行

    Windows安装JRuby Rails 直接下载 JRuby,不装 Ruby. http://jruby.org/download 该安装包可以配好环境变量 %JRUBY_HOME% 等 安装 bu ...

  3. 如何灵活运用Linux 进程资源监控和进程限制

    导读 每个 Linux 系统管理员都应该知道如何验证硬件.资源和主要进程的完整性和可用性.另外,基于每个用户设置资源限制也是其中一项必备技能. 在这篇文章中,我们会介绍一些能够确保系统硬件和软件正常工 ...

  4. kernel 4.4.12 EETI eGTouch 电容屏驱动移植

    kernel 4.4.12 EETI eGTouch 电容屏驱动移植: 在make menuconfig 里面添加如下选项: 添加通过事件上报接口节点: Device Drivers ---> ...

  5. LaTeX简单使用方法

    Content LaTeX的用途 LaTeX文件布局 LaTeX的文档格式 公式环境 图的排版 表格的排版 有序列表和无序列表 引用 伪代码 参考文献 LaTeX的用途 LaTeX是一种基于TeX的排 ...

  6. Spring框架学习(一)

    一. spring概述 Spring 框架是一个分层架构,由 7 个定义良好的模块组成.Spring 模块构建在核心容器之上,核心容器定义了创建.配置和管理 bean 的方式,如图 1 所示. 图 1 ...

  7. python --enable-shared

    https://github.com/docker-library/python/issues/21 例如编译安装python3.5.2,脚本如下: wget https://s3.cn-north- ...

  8. MicrosoftWord2013基本用法

    MicrosoftWord2013基本用法 Word联机使用 自定义工作区 单击"文件"选项,单击"自定义功能区".显示的就是我们编辑文档时上方的工具栏所有选项 ...

  9. 设计模式--代理模式Proxy(结构型)

    一.代理模式 为其他对象提供一种代理以控制对这个对象的访问. 代理模式分为四种: 远程代理:为了一个对象在不同的地址空间提供局部代表.这样可以隐藏一个对象存在于不同地址空间的事实. 虚拟代理:根据需要 ...

  10. Reverse Core 第三部分 - 21章 - Windows消息钩取

    @author: dlive @date: 2016/12/19 0x01 SetWindowsHookEx() HHOOK SetWindowsHookEx( int idHook, //hook ...