Netty:ChannelFuture

lingjiango發表於2020-04-18

上一篇我們完成了對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結果的問題。

相關文章