本文地址: juejin.im/post/5df771…
說在前面
本文的 Netty原始碼使用的是 4.1.31.Final
版本,不同版本會有一些差異.
JDK Future
在說JDK的非同步Future之前,先簡單介紹一下JDK自帶的Future機制.
首先先上一段程式碼
public class JDKFuture {
static ExecutorService executors = new ThreadPoolExecutor(1,
1,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(16));
public static void main(String[] args) throws Exception{
int cnt = 1;
Future[] jdkFuture=new Future[cnt];
Object jdkFutureResult;
for(int i = 0;i < cnt; i++){
jdkFuture[i] = executors.submit(new JDKCallable(i));
}
System.out.println(String.format("%s 在 %s 即將獲取任務執行結果", Thread.currentThread(), new Date()));
jdkFutureResult = jdkFuture[0].get();
System.out.println(String.format("%s 在 %s 任務結果獲取完畢 %s", Thread.currentThread(), new Date(), jdkFutureResult));
executors.shutdown();
}
static class JDKCallable implements Callable{
int index;
JDKCallable(int ind){
this.index = ind;
}
public Object call() throws Exception {
try {
System.out.println(String.format("執行緒 [%s] 提交任務[%s]", Thread.currentThread(), this.index));
// 耗時2秒,模擬耗時操作
Thread.sleep(2000);
System.out.println(String.format("執行緒 [%s] 執行任務[%s]執行完畢", Thread.currentThread(), this.index));
}catch(InterruptedException e){
e.printStackTrace();
}
return String.format("任務%s執行結果",this.index);
}
}
}
複製程式碼
輸出結果為:
執行緒 [Thread[pool-1-thread-1,5,main]] 提交任務[0]
Thread[main,5,main] 在 Mon Dec 16 16:40:38 CST 2019 即將獲取任務執行結果
執行緒 [Thread[pool-1-thread-1,5,main]] 執行任務[0]執行完畢
Thread[main,5,main] 在 Mon Dec 16 16:40:40 CST 2019 任務結果獲取完畢 任務0執行結果
複製程式碼
可以看到主執行緒在使用 future.get()
的時候,因為子執行緒還未處理完返回結果而導致主執行緒活生生的等了2秒鐘(耗時操作),這也是JDK自帶的Future機制不夠完善的地方.因為jdk自身的future機制不夠完善,所以Netty自實現了一套Future機制.
Netty 非同步Future/Promise
Netty的Future是非同步的,那他是怎麼實現的呢?接下來就從原始碼開始探究.
先看一下 Netty 的 Future
和 Promise
這兩個介面
Future
/**
* The result of an asynchronous operation
* 非同步操作的結果
* 對狀態的判斷、新增listener、獲取結果
*/
public interface Future<V> extends java.util.concurrent.Future<V> {
boolean isSuccess();
boolean isCancellable();
Throwable cause();
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
Future<V> sync() throws InterruptedException;
Future<V> syncUninterruptibly();
Future<V> await() throws InterruptedException;
Future<V> awaitUninterruptibly();
boolean await(long timeout, TimeUnit unit) throws InterruptedException;
boolean await(long timeoutMillis) throws InterruptedException;
boolean awaitUninterruptibly(long timeout, TimeUnit unit);
boolean awaitUninterruptibly(long timeoutMillis);
V getNow();
@Override
boolean cancel(boolean mayInterruptIfRunning);
}
複製程式碼
Promise
Promise是一個特殊的Future,它可寫,可寫意味著可以修改裡面的結果.
/**
* Special {@link Future} which is writable.
* 一個可寫的特殊的Future
* 繼承 Future, 繼承的方法就不列出
*/
public interface Promise<V> extends Future<V> {
/**
* Marks this future as a success and notifies all
* listeners.
* If it is success or failed already it will throw an {@link IllegalStateException}.
* 將這個 future 標記為 success 並且通知所有的 listeners
* 如果已經成功或者失敗將會丟擲異常
*/
Promise<V> setSuccess(V result);
/**
* Marks this future as a success and notifies all
* listeners.
*
* @return {@code true} if and only if successfully marked this future as
* a success. Otherwise {@code false} because this future is
* already marked as either a success or a failure.
* 嘗試設定結果,成功返回true, 失敗 false, 上面的方法設定失敗會丟擲異常
*/
boolean trySuccess(V result);
// 這2個跟上面的差不多
Promise<V> setFailure(Throwable cause);
boolean tryFailure(Throwable cause);
/**
* Make this future impossible to cancel.
*
* @return {@code true} if and only if successfully marked this future as uncancellable or it is already done
* without being cancelled. {@code false} if this future has been cancelled already.
*/
boolean setUncancellable();
}
複製程式碼
原始碼解讀
看到這裡都同學都預設是用netty寫過程式的~,還沒寫過的話可以看看官方文件或者我的另一篇Netty使用.
接下來就開始原始碼的解讀.
那麼從哪裡開始呢?
總所周知!
,我們使用Netty開發的時候,寫出資料用的是 writeAndFlush(msg)
, 至於 write(msg)
嘛, 不就是少了個 flush (沒錯,是我比較懶).
開始
在大家知道 channel().write
和 ctx.write
的區別後, 我們就從 channel().write
開始講起.
不行,我感覺還是要說一下一些補充的,不然心裡不舒服.
Netty中有一個pipeline
,也就是事件呼叫鏈,開發的時候在呼叫鏈裡面加入自己處理事件的handle,但是在這條 pipeline 中, Netty給我們加上了 Head
和 tail
這兩個handle,方便Netty框架處理事件.
先看 DefaultChannelPipeline 的初始化,在初始化程式碼裡給我們新增了2個handle, head 和 tail, 這2個東西很有用,為什麼這麼說呢?詳情看後面解答
protected DefaultChannelPipeline(Channel channel) {
this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");
this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);
this.voidPromise = new VoidChannelPromise(channel, true);
// ChannelInboundHandler
this.tail = new DefaultChannelPipeline.TailContext(this);
// ChannelInboundHandler && ChannelOutboundHandler
this.head = new DefaultChannelPipeline.HeadContext(this);
this.head.next = this.tail;
this.tail.prev = this.head;
}
複製程式碼
Real 開始
沒錯,還是從 channel().write(msg)
開始說起(為什麼我要用還是).
跟蹤程式碼 channel().write(), 首先會呼叫到 DefaultChannelPipeline的 writeAndFlush 方法.
1.DefaultChannelPipeline#writeAndFlush
public final ChannelFuture writeAndFlush(Object msg) {
return this.tail.writeAndFlush(msg);
}
複製程式碼
this.tail 就是上面建構函式裡面初始化的 tailHandle
, 而 write
是出棧事件, 會從 tailHandle 開始往前傳遞,最後傳遞到 headHandle
(怎麼感覺好像提前劇透了).
public ChannelFuture writeAndFlush(Object msg) {
// 這裡new了一個 promise, 然後這個promise將會一直傳遞,一直傳遞.....
return this.writeAndFlush(msg, this.newPromise());
}
複製程式碼
接下來來到了AbstractChannelHandlerContext
的 writeAndFlush.
/**
* 執行 write and flush 操作
* @param msg
* @param promise
*/
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
// 這個方法在 ChannelHandler#handlerAdded 呼叫後,才會返回 true
if (invokeHandler()) {
// write 繼續傳遞
invokeWrite0(msg, promise);
// flush data
invokeFlush0();
} else {
writeAndFlush(msg, promise);
}
}
private void write(Object msg, boolean flush, ChannelPromise promise) {
// 查詢下一個 OutboundHandle, 因為是要輸出
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
// 下一個 OutboundHandle 所在的執行緒
EventExecutor executor = next.executor();
// 如果在是同一個執行緒(由於Netty的channel在一個ThreadPool中只繫結一個Thread, 不同執行緒的話也意味著是不同執行緒池)
if (executor.inEventLoop()) {
// 在同一個執行緒池(這裡意味著同一個執行緒)中,
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
// 在不同執行緒池(不同執行緒池那自然就是不同執行緒),需要建立一個任務,提交到下一個執行緒池
final AbstractWriteTask task;
if (flush) {
// 提交給下一個執行緒池 && flush
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
// 因為是 write 事件, so 接下來提交任務到下一個 OutboundHandle(出棧) 所在的執行緒, 由它執行
if (!safeExecute(executor, task, promise, m)) {
// We failed to submit the AbstractWriteTask. We need to cancel it so we decrement the pending bytes
// and put it back in the Recycler for re-use later.
//
// See https://github.com/netty/netty/issues/8343.
// 任務提交失敗,取消任務
task.cancel();
}
}
}
複製程式碼
2.HeadContext#write、flush
接下來本篇文章最重要的地方了, HeadContext !
HeadContext的write和flush方法 實際上都是呼叫 unsafe的方法實現.
write
// 如果是 writeAndFlush ,呼叫 write後會呼叫flush
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// 這個呼叫 AbstrachUnsafe.write
unsafe.write(msg, promise);
}
// 這是 unsafe 的 write 方法
@Override
public final void write(Object msg, ChannelPromise promise) {
assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
// outboundBuffer = null 表明 channel已經關閉並且需要將 future 結果設定為 false
if (outboundBuffer == null) {
// If the outboundBuffer is null we know the channel was closed and so
// need to fail the future right away. If it is not null the handling of the rest
// will be done in flush0()
// See https://github.com/netty/netty/issues/2362
safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
// release message now to prevent resource-leak
ReferenceCountUtil.release(msg);
return;
}
int size;
try {
msg = filterOutboundMessage(msg);
size = pipeline.estimatorHandle().size(msg);
if (size < 0) {
size = 0;
}
} catch (Throwable t) {
safeSetFailure(promise, t);
ReferenceCountUtil.release(msg);
return;
}
// 將 msg新增進 buffer 中
outboundBuffer.addMessage(msg, size, promise);
}
複製程式碼
flush
如果是WriteAndFlush, 則在呼叫write後,會呼叫Head的flush方法,同 write是呼叫AbstractUnsafe的flush
/**
* write 之後再呼叫這個 flush
*/
@Override
public final void flush() {
assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
return;
}
// buffer 標記為可以被 flush
outboundBuffer.addFlush();
// 接下來就是真正的 flush
flush0();
}
複製程式碼
ChannelOutboundBuffer 是個啥呢?
ChannelOutboundBuffer 簡單來說就是儲存當前channel寫出的資料
, 並且在呼叫flush的時候將他們都寫出去.
跟著原始碼一直走,在 flush0
之後,最終會呼叫到 AbstractNioMessageChannel#doWrite
方法.(上面還有doRead方法,是接收資料的時候呼叫的)
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
final SelectionKey key = selectionKey();
final int interestOps = key.interestOps();
for (;;) {
//
Object msg = in.current();
if (msg == null) {
// Wrote all messages.
// 判斷寫事件
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
}
break;
}
try {
// 迴圈寫出資料
boolean done = false;
for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
// 真正的寫出資料
// 最終會呼叫 javaChannel().send(nioData, mi);
// 很眼熟吧,這個是java nio的方法,註冊的時候也是javaChannel().register()
if (doWriteMessage(msg, in)) {
done = true;
break;
}
}
// 成功寫出,從 buffer 中移除剛才寫出的資料
if (done) {
in.remove();
} else {
// Did not write all messages.
// 寫出失敗
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
key.interestOps(interestOps | SelectionKey.OP_WRITE);
}
break;
}
} catch (Exception e) {
// 出錯後是否繼續寫出後面的資料
if (continueOnWriteError()) {
in.remove(e);
} else {
throw e;
}
}
}
}
複製程式碼
3.Promise
到上面位置,資料是寫出去了,那promise的相關作用呢?沒看出來啊?
說實話,這個藏得挺深,居然! 放在了 buffer.remove() 裡
!
public boolean remove() {
// 剛寫出去資料的Entry
Entry e = flushedEntry;
if (e == null) {
clearNioBuffers();
return false;
}
Object msg = e.msg;
// 這個就是writeAndFlush 的時候 new 的 DefaultPromise()
ChannelPromise promise = e.promise;
int size = e.pendingSize;
// buffer 中移除
removeEntry(e);
if (!e.cancelled) {
// only release message, notify and decrement if it was not canceled before.
ReferenceCountUtil.safeRelease(msg);
// !!! 劃重點 !!!
// 這裡設定了 promise 的結果, 呼叫了 trySuccess, 通知所有 listener
// !!! 劃重點 !!!
safeSuccess(promise);
decrementPendingOutboundBytes(size, false, true);
}
// recycle the entry
// 重置Entry的資訊,方便重用.
// 跟 Entry entry = Entry.newInstance(msg, size, total(msg), promise); 相對應, newInstance 是獲取一個快取的 Entry
e.recycle();
return true;
}
複製程式碼
promise 通知所有 listener 是在寫資料成功,並且在 buffer.remove()
呼叫的時候在裡面 safeSuccess(promise)
, 最終呼叫 Promise 的 trySuccess()
從而觸發 notifyListeners()
通知所有 listeners.
4.NotifyListener
這個是在 Promise#trySuccess的時候呼叫的,通知所有listeners操作已經完成.
/**
* 通知監聽者,任務已經完成
*/
private void notifyListeners() {
// 獲取future所屬執行緒(池)
EventExecutor executor = executor();
// 執行通知是當前執行緒 則直接回撥資訊
// currentThread == this.executor
if (executor.inEventLoop()) {
// 獲取 ThreadLocal 變數
final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
// listen 的層級數
final int stackDepth = threadLocals.futureListenerStackDepth();
if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
threadLocals.setFutureListenerStackDepth(stackDepth + 1);
try {
// 通知所有的 listener
notifyListenersNow();
} finally {
threadLocals.setFutureListenerStackDepth(stackDepth);
}
return;
}
}
// 如果 executor 不是當前執行緒, 則交給 future 所屬 executor 去執行
// 意思是新增通知的 executor 可能是前面的 executor , 然後到後面的 executor 也就是當前執行緒才執行通知
// 此時將通知交回給之前的 executor
// 執行通知的不是當前執行緒, 封裝成一個任務, 由之前提交的執行緒完成通知(回撥)
safeExecute(executor, new Runnable() {
@Override
public void run() {
notifyListenersNow();
}
});
}
複製程式碼
總結
Netty 的 Future 非同步機制是在操作完成後,將通知封裝成Task,由Promise所屬執行緒(Executors)執行(回撥).
最後
這次的內容到這裡就結束了,最後的最後,非常感謝你們能看到這裡!!你們的閱讀都是對作者的一次肯定!!!
覺得文章有幫助的看官順手點個贊再走唄(終於暴露了我就是來騙讚的(◒。◒)),你們的每個贊對作者來說都非常重要(異常真實),都是對作者寫作的一次肯定(double)!!!