上一篇咱們完成了對Channel的學習,這一篇讓咱們來學習一下ChannelFuture。html
ChannelFuture的簡介編程
ChannelFuture是Channel異步IO操做的結果。異步
Netty中的全部IO操做都是異步的。這意味着任何IO調用都將當即返回,而不能保證所請求的IO操做在調用結束時完成。相反,將返回一個帶有ChannelFuture的實例,該實例將提供有關IO操做的結果或狀態的信息。ide
ChannelFuture要麼是未完成狀態,要麼是已完成狀態。IO操做剛開始時,將建立一個新的Future對象。新的Future對象最初處於未完成的狀態,由於IO操做還沒有完成,因此既不會執行成功、執行失敗,也不會取消執行。若是IO操做由於執行成功、執行失敗或者執行取消致使操做完成,則將被標記爲已完成的狀態,並帶有更多特定信息,例如失敗緣由。請注意,即便執行失敗和取消執行也屬於完成狀態。post
ChannelFuture提供了各類方法,可以讓您檢查IO操做是否已完成,等待完成以及獲取IO操做的結果。它還容許您添加ChannelFutureListener,以便在IO操做完成時獲得通知。性能
Prefer addListener(GenericFutureListener) to await()學習
推薦使用addListener(GenericFutureListener)而不是await(),以便在完成IO操做並執行任何後續任務時獲得通知。spa
addListener(GenericFutureListener)是非阻塞的。它只是將指定的ChannelFutureListener添加到ChannelFuture,而且與未來關聯的IO操做完成時,IO線程將通知監聽器。ChannelFutureListener徹底不阻塞,所以可產生最佳的性能和資源利用率,可是若是不習慣事件驅動的編程,則實現順序邏輯可能會比較棘手。線程
相反,await()是阻塞操做。一旦被調用,調用者線程將阻塞直到操做完成。使用await()實現順序邏輯比較容易,可是調用者線程會沒必要要地阻塞直到完成IO操做爲止,而且線程間通知的成本相對較高。此外,在特定狀況下還可能出現死鎖。 code
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大問題:
一、isDone()的定義模糊不清,不論是失敗、異常仍是成功,isDone()返回的都是true;
二、get()獲取結果的方式是阻塞等待的方式。
因此Netty中的Future對JDK中的Future作了擴展,而ChannelFuture繼承Future,當然也能充分利用這個擴展出的新特性。新特性主要體如今以下兩方面:
一、引入isSuccess()來表示執行成功,引入cause()來表示執行失敗的緣由;
二、引入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,最後總結一下:
一、Netty中的全部IO操做都是異步的。這意味着任何IO調用都將當即返回,而不能保證所請求的IO操做在調用結束時完成。ChannelFuture是Channel異步IO操做的結果;
二、ChannelFuture或者說是Future,經過引入新的特性解決了原生JDK中Future對於狀態模糊不清及阻塞等待獲取結果的方式,這個新特性就是引入isSuccess()、cause()方法,同時經過Future-Listener回調機制解決不知道什麼時候能獲取到Future結果的問題。