上两篇文章 java基础:Future简介 和 java基础:Future原理 介绍了JDK的Future的功能和基本原理,可以发现JDK的Future只能通过get方法同步阻塞获取结果,能不能异步的获取结果呢?也就是,能不能不阻塞调用线程(主线程),当被调用线程(业务线程)执行完结果,将结果回调给需要结果的代码?JDK的Future只能抛出ExecutionException,能不能直接获得任务执行异常的原因?下面依次介绍分析。
先用一句话总结下JDK Future的原理:相当于将要执行的任务封装成一个FutureTask,将该任务放入线程池执行的时候,执行的其实是FutureTask的run方法,FutureTask的run方法执行完任务之后,设置任务的执行结果。这样主线程和业务线程相当于通过FutureTask对象建立了通信渠道,这样主线程就能获得业务线程的执行结果了。显然主线程需要被阻塞等待在业务线程执行的任务执行完。
然而,Netty的Future完全不是这样的。虽然它也有PromiseTask,但是却不是一个公有类,仅仅是内部使用。Netty的Future完全通过监听器的方式获得任务执行的结果或者异常。那么Netty怎么设置结果呢?JDK有FutureTask可以在线程间传递结果。Netty通过什么?Netty通过Promise编码的方式设置结果,并回调监听器。
一、Netty的Future
1. Future
netty的Future接口直接继承自JDK的Future,增加了部分功能:
/**
* The result of an asynchronous operation.
*/
// 继承自JDK的Future
@SuppressWarnings("ClassNameSameAsAncestorName")
public interface Future<V> extends java.util.concurrent.Future<V> {
// 忽略一些简单方法,可查看源码
/**
* Returns the cause of the failed I/O operation if the I/O operation has
* failed.
*
* @return the cause of the failure.
* {@code null} if succeeded or this future is not
* completed yet.
*/
Throwable cause();
/**
* Adds the specified listener to this future. The
* specified listener is notified when this future is
* {@linkplain #isDone() done}. If this future is already
* completed, the specified listener is notified immediately.
*/
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
/**
* Removes the first occurrence of the specified listener from this future.
* The specified listener is no longer notified when this
* future is {@linkplain #isDone() done}. If the specified
* listener is not associated with this future, this method
* does nothing and returns silently.
*/
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
/**
* Waits for this future until it is done, and rethrows the cause of the failure if this future
* failed.
*/
Future<V> sync() throws InterruptedException;
Future<V> syncUninterruptibly();
/**
* Waits for this future to be completed.
*
* @throws InterruptedException
* if the current thread was interrupted
*/
Future<V> await() throws InterruptedException;
/**
* Waits for this future to be completed without
* interruption. This method catches an {@link InterruptedException} and
* discards it silently.
*/
Future<V> awaitUninterruptibly();
/**
* Return the result without blocking. If the future is not done yet this will return {@code null}.
*
* As it is possible that a {@code null} value is used to mark the future as successful you also need to check
* if the future is really done with {@link #isDone()} and not relay on the returned {@code null} value.
*/
V getNow();
/**
* {@inheritDoc}
*
* If the cancellation was successful it will fail the future with an {@link CancellationException}.
*/
@Override
boolean cancel(boolean mayInterruptIfRunning);
}
这里面忽略了一些方法,只罗列了会详细讨论的方法。
可以看到Netty的Future增加了监听器模式的Listener,方便回调;Future可以通过cause方法获取任务执行异常的原因;主线程也可以通过sync()或者await()方法阻塞等待异步任务执行结束。
2. ChannelFuture
Netty是一个网络框架,肯定要为网络channel建立联系,ChannelFuture就是做这个事情的。
/**
* The result of an asynchronous {@link Channel} I/O operation.
* <p>
* All I/O operations in Netty are asynchronous. It means any I/O calls will
* return immediately with no guarantee that the requested I/O operation has
* been completed at the end of the call. Instead, you will be returned with
* a {@link ChannelFuture} instance which gives you the information about the
* result or status of the I/O operation.
* <p>
* A {@link ChannelFuture} is either <em>uncompleted</em> or <em>completed</em>.
* When an I/O operation begins, a new future object is created. The new future
* is uncompleted initially - it is neither succeeded, failed, nor cancelled
* because the I/O operation is not finished yet. If the I/O operation is
* finished either successfully, with failure, or by cancellation, the future is
* marked as completed with more specific information, such as the cause of the
* failure. Please note that even failure and cancellation belong to the
* completed state.
* <pre>
* +---------------------------+
* | Completed successfully |
* +---------------------------+
* +----> isDone() = true |
* +--------------------------+ | | isSuccess() = true |
* | Uncompleted | | +===========================+
* +--------------------------+ | | Completed with failure |
* | isDone() = false | | +---------------------------+
* | isSuccess() = false |----+----> isDone() = true |
* | isCancelled() = false | | | cause() = non-null |
* | cause() = null | | +===========================+
* +--------------------------+ | | Completed by cancellation |
* | +---------------------------+
* +----> isDone() = true |
* | isCancelled() = true |
* +---------------------------+
* </pre>
*
* Various methods are provided to let you check if the I/O operation has been
* completed, wait for the completion, and retrieve the result of the I/O
* operation. It also allows you to add {@link ChannelFutureListener}s so you
* can get notified when the I/O operation is completed.
*
* <h3>Prefer {@link #addListener(GenericFutureListener)} to {@link #await()}</h3>
*
* It is recommended to prefer {@link #addListener(GenericFutureListener)} to
* {@link #await()} wherever possible to get notified when an I/O operation is
* done and to do any follow-up tasks.
* <p>
* {@link #addListener(GenericFutureListener)} is non-blocking. It simply adds
* the specified {@link ChannelFutureListener} to the {@link ChannelFuture}, and
* I/O thread will notify the listeners when the I/O operation associated with
* the future is done. {@link ChannelFutureListener} yields the best
* performance and resource utilization because it does not block at all, but
* it could be tricky to implement a sequential logic if you are not used to
* event-driven programming.
* <p>
* By contrast, {@link #await()} is a blocking operation. Once called, the
* caller thread blocks until the operation is done. It is easier to implement
* a sequential logic with {@link #await()}, but the caller thread blocks
* unnecessarily until the I/O operation is done and there's relatively
* expensive cost of inter-thread notification. Moreover, there's a chance of
* dead lock in a particular circumstance, which is described below.
*
* <h3>Do not call {@link #await()} inside {@link ChannelHandler}</h3>
* <p>
* The event handler methods in {@link ChannelHandler} are usually called by
* an I/O thread. If {@link #await()} is called by an event handler
* method, which is called by the I/O thread, the I/O operation it is waiting
* for might never complete because {@link #await()} can block the I/O
* operation it is waiting for, which is a dead lock.
* <pre>
* // BAD - NEVER DO THIS
* {@code @Override}
* public void channelRead({@link ChannelHandlerContext} ctx, Object msg) {
* {@link ChannelFuture} future = ctx.channel().close();
* future.awaitUninterruptibly();
* // Perform post-closure operation
* // ...
* }
*
* // GOOD
* {@code @Override}
* public void channelRead({@link ChannelHandlerContext} ctx, Object msg) {
* {@link ChannelFuture} future = ctx.channel().close();
* future.addListener(new {@link ChannelFutureListener}() {
* public void operationComplete({@link ChannelFuture} future) {
* // Perform post-closure operation
* // ...
* }
* });
* }
* </pre>
* <p>
* In spite of the disadvantages mentioned above, there are certainly the cases
* where it is more convenient to call {@link #await()}. In such a case, please
* make sure you do not call {@link #await()} in an I/O thread. Otherwise,
* {@link BlockingOperationException} will be raised to prevent a dead lock.
*
* <h3>Do not confuse I/O timeout and await timeout</h3>
*
* The timeout value you specify with {@link #await(long)},
* {@link #await(long, TimeUnit)}, {@link #awaitUninterruptibly(long)}, or
* {@link #awaitUninterruptibly(long, TimeUnit)} are not related with I/O
* timeout at all. If an I/O operation times out, the future will be marked as
* 'completed with failure,' as depicted in the diagram above. For example,
* connect timeout should be configured via a transport-specific option:
* <pre>
* // BAD - NEVER DO THIS
* {@link Bootstrap} b = ...;
* {@link ChannelFuture} f = b.connect(...);
* f.awaitUninterruptibly(10, TimeUnit.SECONDS);
* if (f.isCancelled()) {
* // Connection attempt cancelled by user
* } else if (!f.isSuccess()) {
* // You might get a NullPointerException here because the future
* // might not be completed yet.
* f.cause().printStackTrace();
* } else {
* // Connection established successfully
* }
*
* // GOOD
* {@link Bootstrap} b = ...;
* // Configure the connect timeout option.
* <b>b.option({@link ChannelOption}.CONNECT_TIMEOUT_MILLIS, 10000);</b>
* {@link ChannelFuture} f = b.connect(...);
* f.awaitUninterruptibly();
*
* // Now we are sure the future is completed.
* assert f.isDone();
*
* if (f.isCancelled()) {
* // Connection attempt cancelled by user
* } else if (!f.isSuccess()) {
* f.cause().printStackTrace();
* } else {
* // Connection established successfully
* }
* </pre>
*/
public interface ChannelFuture extends Future<Void> {
/**
* Returns a channel where the I/O operation associated with this
* future takes place.
*/
Channel channel();
// 忽略部分API
}
Netty注释的优秀程度与JDK注释有得一拼,都很棒。
Netty的网络IO都是异步的。也就是说,所有对网络IO的操作都是直接返回的,而不等待IO操作是否完成。那么怎么获得IO操作的结果或者状态呢?可以通过IO操作返回的ChannelFuture实例获得IO操作的结果或者状态。
一个ChannelFuture要么是completed要么是uncompleted,对应着isDone()返回true还是false。一个IO操作开始,一个新的ChannelFuture实例就会被创建。这个Future被初始化为uncompleted,既不是成功、失败,也不是被取消。IO操作一旦成功、失败、被取消,那么这个Future就是completed状态。
Netty建议使用Listener方式而不是wait方式。Listener方式完全是异步的。而wait方式会阻塞主线程直到IO操作完成。比较有意思的是,每次创建Netty的Future都需要传递一个EventExecutor,其实这个EventExecutor就是回调Listener的线程。也就是IO操作不一定要在EventExecutor这个线程中,但是最终的回调肯定是在EventExecutor线程中(回调时进行了线程切换)。
注释中也举了2个例子:
-
千万不要在IO线程中调用wait方法,有可能会死锁。因为,IO操作在IO线程中执行,而IO线程又在等待IO操作完成。
-
不要混淆IO操作的超时和await的超时。有可能await超时了,但是IO操作还没有完成。
二、Netty的Promise
1. Promise
通过Future可以获得IO操作的结果,那么怎么设置IO操作的结果呢?就是通过Promise。
/**
* Special {@link Future} which is writable.
*/
public interface Promise<V> extends Future<V> {
// 忽略一些API
/**
* Marks this future as a success and notifies all
* listeners.
*
* If it is success or failed already it will throw an {@link IllegalStateException}.
*/
Promise<V> setSuccess(V result);
/**
* Marks this future as a failure and notifies all
* listeners.
*
* If it is success or failed already it will throw an {@link IllegalStateException}.
*/
Promise<V> setFailure(Throwable cause);
}
可以看到Promise继承自Netty的Future。
2. ChannelPromise
ChannelPromise跟channel建立了联系。
/**
* Special {@link ChannelFuture} which is writable.
*/
public interface ChannelPromise extends ChannelFuture, Promise<Void> {
@Override
Channel channel();
}
三、Netty的Promise实现:DefaultPromise
1. 构造方法
/**
* Creates a new instance.
*
* It is preferable to use {@link EventExecutor#newPromise()} to create a new promise
*
* @param executor
* the {@link EventExecutor} which is used to notify the promise once it is complete.
* It is assumed this executor will protect against {@link StackOverflowError} exceptions.
* The executor may be used to avoid {@link StackOverflowError} by executing a {@link Runnable} if the stack
* depth exceeds a threshold.
*
*/
public DefaultPromise(EventExecutor executor) {
this.executor = checkNotNull(executor, "executor");
}
如注释所说,参数EventExecutor是用来回调Listener的。而且这个通知回调避免了StackOverflowError,其实就是看当前调用栈的深度有多大(连续嵌套了多少个方法),如果调用栈太深的话,就不回调了!当然,这个要看这个任务执行在哪个线程中。如果任务就不是在EventExecutor中执行的,自然就不存在调用栈深度的问题了,因为回调进行了线程切换。
2. setSuccess
@Override
public Promise<V> setSuccess(V result) {
if (setSuccess0(result)) {
notifyListeners();
return this;
}
// 这里抛出了异常
throw new IllegalStateException("complete already: " + this);
}
@Override
public boolean trySuccess(V result) {
if (setSuccess0(result)) {
notifyListeners();
return true;
}
// 这里返回了false
return false;
}
private boolean setSuccess0(V result) {
// private static final Object SUCCESS = new Object();
return setValue0(result == null ? SUCCESS : result);
}
private boolean setValue0(Object objResult) {
// 判断当前状态
if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
// 如果有wait,这里就会进行通知了,也就是因为sync或await调用阻塞的线程就会被唤醒。
// 注意这时还没有回调Listener呢。
checkNotifyWaiters();
return true;
}
return false;
}
private void notifyListeners() {
EventExecutor executor = executor();
// 检查当前线程(也就是执行setSuccess()方法的线程)是不是在构造方法传进来的EventExecutor中
// 也就是,Listener肯定是在EventExecutor线程中被回调的
if (executor.inEventLoop()) {
final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
final int stackDepth = threadLocals.futureListenerStackDepth();
// 防止栈溢出,也没什么玄机,就是不通知Listener了。
if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
threadLocals.setFutureListenerStackDepth(stackDepth + 1);
try {
notifyListenersNow();
} finally {
threadLocals.setFutureListenerStackDepth(stackDepth);
}
return;
}
}
safeExecute(executor, new Runnable() {
@Override
public void run() {
notifyListenersNow();
}
});
}
3. get
get方法一般在Listener中进行。
@Override
public V get() throws InterruptedException, ExecutionException {
// 等待完成
await();
// 是否异常
Throwable cause = cause();
if (cause == null) {
// 获取结果
return getNow();
}
if (cause instanceof CancellationException) {
throw (CancellationException) cause;
}
throw new ExecutionException(cause);
}
@Override
public boolean isDone() {
return isDone0(result);
}
private static boolean isDone0(Object result) {
// 假设上面执行了setSuccess方法
return result != null && result != UNCANCELLABLE;
}
@Override
public Throwable cause() {
Object result = this.result;
return (result instanceof CauseHolder) ? ((CauseHolder) result).cause : null;
}
@SuppressWarnings("unchecked")
@Override
public V getNow() {
Object result = this.result;
if (result instanceof CauseHolder || result == SUCCESS || result == UNCANCELLABLE) {
return null;
}
return (V) result;
}
这样通过get方法就获得结果了。可以看到有个getNow方法,可以直接获得结果,而不等待是否执行完。
4. setFailure
@Override
public Promise<V> setFailure(Throwable cause) {
if (setFailure0(cause)) {
notifyListeners();
return this;
}
throw new IllegalStateException("complete already: " + this, cause);
}
@Override
public boolean tryFailure(Throwable cause) {
if (setFailure0(cause)) {
notifyListeners();
return true;
}
return false;
}
private boolean setFailure0(Throwable cause) {
return setValue0(new CauseHolder(checkNotNull(cause, "cause")));
}
看完了setSuccess,setFailure就很简单了。cause()方法也很简单了。
5. sync
@Override
public Promise<V> sync() throws InterruptedException {
await();
rethrowIfFailed();
return this;
}
@Override
public Promise<V> syncUninterruptibly() {
//
awaitUninterruptibly();
// 抛出异常
rethrowIfFailed();
return this;
}
private void rethrowIfFailed() {
Throwable cause = cause();
if (cause == null) {
return;
}
PlatformDependent.throwException(cause);
}
可以看到sync里面调用的就是await方法。然后又判断了一下是否需要抛出异常。
6. await
@Override
public Promise<V> await() throws InterruptedException {
if (isDone()) {
return this;
}
// 如果被中断,继续抛出InterruptedException异常
if (Thread.interrupted()) {
throw new InterruptedException(toString());
}
checkDeadLock();
synchronized (this) {
while (!isDone()) {
incWaiters();
try {
wait();
} finally {
decWaiters();
}
}
}
return this;
}
// 一直没有抛出InterruptedException
@Override
public Promise<V> awaitUninterruptibly() {
if (isDone()) {
return this;
}
checkDeadLock();
boolean interrupted = false;
synchronized (this) {
while (!isDone()) {
incWaiters();
try {
wait();
} catch (InterruptedException e) {
// Interrupted while waiting.
interrupted = true;
} finally {
decWaiters();
}
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return this;
}
四、测试代码
public class NettyFutureTest {
public static void main(String[] args) {
System.out.println("current thread: " + Thread.currentThread().isDaemon());
EventExecutor eventExecutor = new DefaultEventExecutor(new DefaultThreadFactory("promise", true));
Promise<String> promise = eventExecutor.newPromise();
promise.addListener(new GenericFutureListener<Future<? super String>>() {
@Override
public void operationComplete(Future<? super String> future) throws Exception {
System.out.println("complete....");
if (future.isSuccess()) {
System.out.println("future is success, result: " + future.get());
} else {
System.out.println("future is failed, result: " + future.cause());
}
}
});
Executors.newSingleThreadExecutor(new DefaultThreadFactory("biz", true)).execute(new Runnable() {
@Override
public void run() {
System.out.println("task is running...");
sleep(800);
if (promise.isCancelled()) {
System.out.println("promise has been canceled...");
} else {
promise.setSuccess("ok");
}
System.out.println("biz execute over...");
}
});
// eventExecutor.execute(new Runnable() {
// @Override
// public void run() {
// System.out.println("task is running...");
// sleep(800);
// if (promise.isCancelled()) {
// System.out.println("promise has been canceled...");
// } else {
// promise.setSuccess("ok");
// }
// System.out.println("biz execute over...");
// }
// });
// sleep(100);
// boolean canceled = promise.cancel(true);
// System.out.println("promise cancel result: " + canceled);
// Thread.currentThread().interrupt();
// System.out.println("main thread isInterrupted: " + Thread.currentThread().isInterrupted());
try {
promise.sync();
} catch (InterruptedException e) {
System.out.println("here...");
e.printStackTrace();
}
System.out.println("main over...");
}
private static void sleep(long timeMs) {
try {
TimeUnit.MILLISECONDS.sleep(timeMs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面代码的执行结果:
// main线程不是daemon线程
current thread: false
// 业务开始执行
task is running...
// 可以发现setSuccess之后,main线程就被唤醒退出了
main over...
// 业务执行结束
biz execute over...
// 在EventExecutor中回调Listener
// 其实我总觉着,后面两个可能不会输出。
// 因为main线程都退出了,而EventExecutor是个daemon线程,所以可能调度不上。
complete....
future is success, result: ok
再说一句,DefaultEventExecutor的run方法是一个死循环。
上面的测试程序,有很多被注释的代码。可以分别执行执行试试。
硬广告
欢迎关注公众号:double6