上一篇我們完成了對Channel的學習,這一篇讓我們來學習一下ChannelFuture。
ChannelFuture的簡介
ChannelFuture是Channel非同步IO操作的結果。
Netty中的所有IO操作都是非同步的。這意味著任何IO呼叫都將立即返回,而不能保證所請求的IO操作在呼叫結束時完成。相反,將返回一個帶有ChannelFuture的例項,該例項將提供有關IO操作的結果或狀態的資訊。
ChannelFuture要麼是未完成狀態,要麼是已完成狀態。IO操作剛開始時,將建立一個新的Future物件。新的Future物件最初處於未完成的狀態,因為IO操作尚未完成,所以既不會執行成功、執行失敗,也不會取消執行。如果IO操作因為執行成功、執行失敗或者執行取消導致操作完成,則將被標記為已完成的狀態,並帶有更多特定資訊,例如失敗原因。請注意,即使執行失敗和取消執行也屬於完成狀態。
ChannelFuture提供了各種方法,可讓您檢查IO操作是否已完成,等待完成以及獲取IO操作的結果。它還允許您新增ChannelFutureListener,以便在IO操作完成時得到通知。
Prefer addListener(GenericFutureListener) to await()
推薦使用addListener(GenericFutureListener)而不是await(),以便在完成IO操作並執行任何後續任務時得到通知。
addListener(GenericFutureListener)是非阻塞的。它只是將指定的ChannelFutureListener新增到ChannelFuture,並且與將來關聯的IO操作完成時,IO執行緒將通知監聽器。ChannelFutureListener完全不阻塞,因此可產生最佳的效能和資源利用率,但是如果不習慣事件驅動的程式設計,則實現順序邏輯可能會比較棘手。
相反,await()是阻塞操作。一旦被呼叫,呼叫者執行緒將阻塞直到操作完成。使用await()實現順序邏輯比較容易,但是呼叫者執行緒會不必要地阻塞直到完成IO操作為止,並且執行緒間通知的成本相對較高。此外,在特定情況下還可能出現死鎖。
Do not call await() inside ChannelHandler
ChannelHandler中的事件處理程式方法通常由IO執行緒呼叫,如果await()是由IO執行緒呼叫的事件處理程式方法呼叫的,則它正在等待的IO操作可能永遠不會完成,因為await()會阻塞它正在等待的IO操作,這是一個死鎖。
// BAD - NEVER DO THIS @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ChannelFuture future = ctx.channel().close(); future.awaitUninterruptibly(); // Perform post-closure operation // ... }
// GOOD @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ChannelFuture future = ctx.channel().close(); future.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) { // Perform post-closure operation // ... } }); }
儘管有上述缺點,但是在某些情況下,呼叫await()更方便。在這種情況下,請確保不要在IO執行緒中呼叫await()。 否則,將引發BlockingOperationException來防止死鎖。
Do not confuse I/O timeout and await timeout
使用await(long),await(long,TimeUnit),awaitUninterruptible(long)或awaitUninterruptible(long,TimeUnit)指定的timeout與IO超時根本不相關。 如果IO操作超時,則Future將被標記為“Completed with failure”,如上圖所示。 例如,應通過特定於傳輸的選項配置連線超時:
// BAD - NEVER DO THIS Bootstrap b = ...; 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 Bootstrap b = ...; // Configure the connect timeout option. b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000); 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 }
ChannelFuture的方法
ChannelFuture的方法並不多,可以簡單的看一下。
channel():返回ChannelFuture關聯的Channel;
addListener():將指定的listener新增到Future。Future完成時,將通知指定的listener。如果Future已經完成,則立即通知指定的listener;
addListeners():和上述方法一樣,只不過此方法可以新增一系列的listener;
removeListener():從Future中刪除第一次出現的指定listener。完成Future時,不再通知指定的listener。如果指定的listener與此Future沒有關聯,則此方法不執行任何操作並以靜默方式返回。
removeListeners():和上述方法一樣,只不過此方法可以移除一系列的listener;
sync():等待Future直到其完成,如果這個Future失敗,則丟擲失敗原因;
syncUninterruptibly():不會被中斷的sync();
await():等待Future完成;
awaitUninterruptibly():不會被中斷的await ();
isVoid():如果此ChannelFuture是void的Future,則返回true,因此不允許呼叫以下任何方法:
addListener(GenericFutureListener)
addListeners(GenericFutureListener[])
await()
await(long, TimeUnit) ()}
await(long) ()}
awaitUninterruptibly()
sync()
syncUninterruptibly()
為什麼使用ChannelFuture?
從JDK1.5之後,J.U.C提供了Future類,它代表著非同步計算的結果。Future類提供瞭如下方法:
方法 |
方法說明 |
boolean cancel(boolean mayInterruptIfRunning) |
嘗試取消執行此任務。如果任務已經完成,已經被取消或由於某些其他原因而無法取消,則此嘗試將失敗。如果成功,並且在呼叫cancel時此任務尚未啟動,則該任務永遠不要執行。如果任務已經啟動,則mayInterruptIfRunning引數確定是否應中斷執行該任務的執行緒以嘗試停止該任務。 此方法返回後,對isDone的後續呼叫將始終返回true。如果此方法返回true,則隨後對isCancelled的呼叫將始終返回true。 |
boolean isCancelled() |
如果此任務在正常完成之前被取消,則返回true。 |
boolean isDone() |
如果此任務完成,則返回true。完成可能是由於正常終止,異常或取消引起的,在所有這些情況下,此方法都將返回true。 |
V get() |
必要時等待計算完成,然後檢索其結果。 |
V get(long timeout, TimeUnit unit) |
必要時最多等待給定時間以完成計算,然後檢索其結果。 |
從這些方法中,可以看出Future類存在2大問題:
1、isDone()的定義模糊不清,不管是失敗、異常還是成功,isDone()返回的都是true;
2、get()獲取結果的方式是阻塞等待的方式。
所以Netty中的Future對JDK中的Future做了擴充套件,而ChannelFuture繼承Future,固然也能充分利用這個擴充套件出的新特性。新特性主要體現在如下兩方面:
1、引入isSuccess()來表示執行成功,引入cause()來表示執行失敗的原因;
2、引入Future-Listener機制來替代主動get()阻塞等待的機制。
對於第1點,可以回到簡介部分,該圖表清晰的描述了這個非同步呼叫的狀態變化。當非同步結果未完成時,isDone()、isSuccess()、isCancelled()均為false,同時cause()返回null,若是完成成功,則isDone()、isSuccess()均為true,若是完成失敗,則isDone()為true,cause()返回not-null,若是取消完成,則
isDone()、isCancelled()均為true。可以看到新引入的特性可以很清晰的表示常用的狀態。
對於第2點,Future-Listener機制本質上就是一種觀察者模式,Netty中的Future通過提供addListener/addListeners方法來實現對Future執行結果的監聽,一旦Future執行完成,就會觸發GenericFutureListener的operationComplete方法,在該方法中就可以獲取Future的執行結果,這種方式比起直接get(),能有效提升Netty的吞吐量。
至此,我們就學習完了ChannelFuture,最後總結一下:
1、Netty中的所有IO操作都是非同步的。這意味著任何IO呼叫都將立即返回,而不能保證所請求的IO操作在呼叫結束時完成。ChannelFuture是Channel非同步IO操作的結果;
2、ChannelFuture或者說是Future,通過引入新的特性解決了原生JDK中Future對於狀態模糊不清及阻塞等待獲取結果的方式,這個新特性就是引入isSuccess()、cause()方法,同時通過Future-Listener回撥機制解決不知道何時能獲取到Future結果的問題。