執行緒阻塞概述
在生活中,最常見的阻塞現象是公路上汽車的堵塞。汽車在公路上快速行駛,如果前方交通受阻,就只好停下來等待,等到公路順暢,才能恢復行駛。
執行緒在執行中也會因為某些原因而阻塞。所有處於阻塞狀態的執行緒的共同特徵:放棄 CPU,暫停執行,只有等到導致阻塞的原因消除,才能恢復執行,或者被其他執行緒中斷該執行緒會退出阻塞狀態,並且丟擲 InterruptedException
導致執行緒阻塞的原因主要有以下方面:
- 執行緒執行了
Threadsleep(int n)
方法,執行緒放棄 CPU,睡眠 n ms,然後恢復執行 - 執行緒要執行一段同步程式碼,由於無法獲得相關的同步鎖,只好進入阻塞狀態,等到獲取同步鎖再恢復執行
- 執行緒執行了一個物件的
wait()
方法,進入阻塞狀態,只有等到其他執行緒執行了該物件的notify()
或notifyAll()
方法,才可能將其喚醒 - 執行緒執行 IO 操作或進行遠端通訊時,會因為等待相關的資源而進入阻塞狀態
進行遠端通訊時,在客戶程式中,執行緒在以下情況下可能進入阻塞狀態:
-
請求與伺服器建立連線時,即當執行緒執行 Socket 的帶引數的構造方法,或執行 Socke 的
connect()
方法時,會進入阻塞狀態,直到連線成功,此執行緒才從 Socket 的構造方法或connect()
方法返回 -
執行緒從 Socket 的輸入流讀入資料時,如果沒有足夠的資料,就會進入阻塞狀態,直到讀到了足夠的資料,或者到達輸入流的末尾,或者出現了異常,才從輸入流的
read()
方法返回或異常中斷輸入流中有多少資料才算足夠呢?這要看執行緒執行的
read()
方法的類:int read()
:只要輸入流中有 1 位元組,就算足夠int read(byte[] buf)
:只要輸入流中的位元組數目與引數 buff 陣列的長度相同,就算足夠String readLine()
:只要輸入流中有 1 行字元,就算足夠
-
執行緒向 Socket 的輸出流寫一批資料時,可能會進入阻塞狀態,等到輸出了所有的資料,或者出現異常,才從輸出流的
write()
方法返回或異常中斷 -
如果呼叫 Socket 的
setSoLinger()
方法設定了關閉 Socket 的延遲時間,那麼當執行緒執行 Socket 的close()
方法時,會進入阻塞狀態,直到底層 Socket 傳送完所有剩餘資料或者超過了setSoLinger()
方法設定的延遲時間,才從close()
方法返回
在伺服器程式中,執行緒在以下情況下可能會進入阻塞狀態:
- 執行緒執行 ServerSocket 的
accept()
方法,等待客戶的連線,直到接收到了客戶連線才從accept()
方法返回 - 執行緒從 Socket 的輸入流讀入資料時,如果輸入流沒有足夠的資料就會進入阻塞狀態
- 執行緒向 Socket 的輸出流寫一批資料時,可能會進入阻塞狀態,等到輸出了所有的資料,或者出現異常,才從輸出流的
write()
方法返回或異常中斷
由此可見,無論是在伺服器程式還是客戶程式中,當透過 Socket 的輸入流和輸出流讀寫資料時,都可能進入阻塞狀態。這種可能出現阻塞的輸入和輸出操作被稱為阻塞 IO。與此對照,如果執行輸入和輸出操作時,不會發生阻塞,則稱為非阻塞 IO
非阻塞通訊的基本思想
假如同時要做兩件事:燒開水和煮粥
燒開水的步驟如下:
鍋子裡放水,開啟煤氣爐
等待水燒開 // 阻塞
關閉煤氣爐,把開水灌到水壺裡
煮粥的步驟如下:
鍋子裡放水和米,開啟煤氣爐
等待粥煮開 // 阻塞
調整煤氣爐,改為小火
等待粥煮熟 // 阻塞
關閉煤氣爐
為了同時完成兩件事,一種方案是同時請兩個人分別做其中的一件事,這相當於採用多執行緒來同時完成多個任務。還有一種方案是讓一個人同時完成兩件事,這個人應該善於利用一件事的空閒時間去做另一件事,這個人一刻也不應該閒著:
鍋子裡放水,開啟煤氣爐 // 開始燒開水
鍋子裡放水和米,開啟煤氣爐 // 開始煮粥
while(一直等待,直到有水燒開、粥煮開或粥煮熟事件發生) { // 阻塞
if(水燒開)
關閉煤氣爐,把開水灌到水壺裡;
if((粥煮開)
調整煤氣爐,改為小火;
if(粥熟)
關閉煤氣爐;
}
這個人不斷監控燒水和煮粥的狀態,如果發生了條件中任一事件就去處理,處理完一件事後繼續監控,直到所有的任務都完成
以上工作方式也可以被運用到伺服器程式中,伺服器程式只需要一個執行緒就能同時接收客戶的連線、接收各個客戶傳送的資料,以及向各個客戶傳送響應資料。伺服器程式的處理流程如下:
while(一直等待,直到有接收連線就緒事件、讀緒事件或寫就緒事件發生) { //阻塞
if(有客戶連線)
接收客戶的連線; // 非阻塞
if(某個socket的輸入流中有可讀資料)
從輸入流中讀資料; // 非阻塞
if(某個socket的輸出流可以寫資料)
向輸出流寫資料; // 非阻塞
}
以上處理流程採用了輪詢的工作方式,當某一種操作就緒,就執行該操作,否則就檢視是否還有其他就緒的操作可以執行。執行緒不會因為某一個操作還沒有就緒,就進入阻塞狀態,一直傻傻地在那裡等待這個操作就緒
為了使輪詢的工作方式順利進行,接收客戶的連線、從輸入流讀資料,以及向輸出流寫資料的操作都應該以非阻寒的方式執行。所謂非阻塞,指當執行緒執行這些方法時,如果操作還沒有就緒,就立即返回,而不會一直等到操作就緒
非阻塞通訊 API
java.nio.channels
包提供了支援非阻塞通訊的類,如下所述:
ServerSocketChannel
:ServerSocket
的替代類,支援阻塞通訊與非阻塞通訊SocketChannel
:Socket
的替代類,支援阻塞通訊與非阻塞通訊Selector
:為ServerSocketChannel
監控接收連線就緒事件,為SocketChannel
監控連
接就緒、讀就緒和寫就緒事件SelectionKey
:代表ServerSocketChannel
以及SocketChannel
向Selector
註冊事件的控制程式碼。當一個SelectionKey
物件位於Selector
物件的selected-keys
集合中,就表示與這個SelectionKey
物件相關的事件發生了
ServerSocketChannel
及 SocketChannel
都是 SelectableChannel
的子類,如圖所示,SelectableChannel
類及其子類都能委託 Selector
來監控它們可能發生的一些事件,這種委託過程也被稱為註冊事件過程
ServerSocketChannel
向 Selector
註冊接收連線就緒事件的程式碼如下:
SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey
類的一些靜態常量表示事件型別,ServerSockerChamnel
只可能發生一種事件:
SelectionKey.OP_ACCEPT
:接收連線緒事件,表示至少有了一個客戶連線,伺服器可以接收這個連線、
SocketChannel
可能發生以下三種事件:
SelectionKey.OP_CONNECT
:連線就緒事件,表示客戶與伺服器的連線已經建立成功SelectionKey.OP_READ
:讀就緒事件,表示輸入流中已經有了可讀資料,可以執行讀操作了SelectionKey.OP_WRITE
: 寫就緒事件,表示已經可以向輸出流寫資料了
SocketChannel
提供了接收和傳送資料的方法:
read(ByteBuffer buffer)
:接收資料,把它們存放到引數指定的 ByteBufferwrite(ByteBuffer buffer)
:把引數指定的 ByteBuffer 中的資料傳送出去
ByteBuffer
表示位元組緩衝區,SocketChannel
的 read()
和 write()
方法都會操縱 ByteBuffer
。ByteBuffer
類繼承於 Buffer
類。ByteBuffer
中存放的是位元組,為了把它們轉換為字串還需要用到 Charset
類,Charset
類代表字元編碼,它提供了把位元組流轉換為字串(解碼過程)和把字串轉換為位元組流(編碼過程)的實用方法
下面分別介紹 Buffer
、Charset
、SelectableChannel
、ServerSocketChannel
、SocketChannel
、Selector
和 SelectionKey
的用法
緩衝區 Buffer
資料輸入和輸出往往是比較耗時的操作,緩衝區從兩個方面提高 I/O 操作的效率:
- 減少實際的物理讀寫次數
- 緩衝區在建立時被分配記憶體,這塊記憶體區域一直被重用,這可以減少動態分配和回收記憶體區域的次數
java.nio
包公開了 Buffer
類的 API,使得 Java 程式可以直接控制和運用緩衝區,所有的緩衝區都有以下屬性:
- 容量(capacity):表示緩衝區可以儲存多少資料
- 極限(limit):表示緩衝區的當前終點,不能對緩衝區中超過極限的區域進行讀寫操作
- 位置(position):表示緩衝區中下一個讀寫單元的位置
以上三個屬性的關係為:容量 > 極限 >= 位置 >= 0
緩衝區提供了用於改變以上三個屬性的方法:
// 把極限設為容量,把位置設為0
clear();
// 把極限設為位置,把位置設為 0
flip();
// 不改變極限,把位置設為0
rewind();
Buffer
類的 remaining()
方法返回緩衝區的剩餘容量,取值等於 極限 - 位置
Buffer
類的 compact()
方法刪除緩衝區內從 0 到當前位置 position 的內容,然後把從當前位置 position 到極限limit 的內容複製到 0 到 limit - position 的區域內
java.nio.Buffer
類是一個抽象類,不能被例項化。它共有 8 個具體的緩衝區類,其中最基本的緩衝區是 ByteBuffer
,它存放的資料單元是位元組,ByteBufer
類並沒有提供公開的構造方法,但是提供了兩個獲得 ByteBuffer
例項的靜態工廠方法:
// 返回一個ByteBuffer物件,引數capacity指定緩衝區的容量
allocate(int capacity);
// 返回一個ByteBuffer物件,引數capacity指定緩衝區的容量
// 該方法返回的緩衝區被稱為直接緩衝區,能進一步提高 I/O 操作的速度
// 分配直接緩衝區的系統開銷很大,因此只有在緩衝區較大並且長期存在,或經常重用時,才使用該緩衝區
directAllocate(int capacity);
除 boolean 型別以外,每種基本型別都有對應的緩衝區類,包括 CharBuffer
,DoubleBuffer
,FloatBuffer
,IntBuffer
,LongBuffer
,ShortBuffer
。在 CharBuffer
中存放的資料單元為字元,以此類推。還有一種緩衝區是 MappedByteBuffer
,它是 ByteBuffer
的子類,能夠把緩衝區和檔案的某個區域直接對映
所有具體緩衝區類都提供了讀寫緩衝區的方法:
// 相對讀,從緩衝區的當前位置讀取一個單元的資料,讀完後把位置加1
get();
// 絕對讀,從引數 index 指定的位置讀取一個單元的資料
get(int index);
// 相對寫,向緩衝區的當前位置寫一個單元的資料,寫完後把位置加1
put(單後設資料型別 data);
// 絕對寫,向引數index指定的位置寫入一個單元的資料
put(int index, 單後設資料型別 data);
ByteBuffer
類不僅可以讀取和寫入一個單元的位元組,還可以讀取和寫入 int、char、float 和 double 等基本型別的資料,例如:
getInt()
getInt(int index)
以上不帶 index 引數的方法會在當前位置讀取或寫入資料,稱為相對讀寫。帶 index 引數的方法會在 index 引數指定的位置讀取或寫入資料,稱為絕對讀寫
字元編碼 Charset
java.nio.Charset
類的每個例項代表特定的字元編碼型別,把位元組序列轉換為字串的過程稱為解碼,把字串轉換為位元組序列的過程稱為編碼
Charset
類提供了編碼與解碼的方法:
// 對引數str指定的字串進行編碼,把得到的位元組序列存放在一個ByteBuffer物件並將其返回
ByteBuffer encode(String str);
// 對引數cb指定的字元緩衝區中的字元進行編碼,把得到的位元組序列存放在一個ByteBuffer物件並將其返回
ByteBuffer encode(CharBuffer cb);
// 對引數bb指定的ByteBuffer的位元組序列進行解碼,把得到的字元序列存放在一個CharBuffer物件並將其返回
CharBuffer decode(ByteBuffer bb);
Charset
類的靜態 forName(String encode)
方法返回一個 Charset
物件,引數 encode
指定編碼型別。例如以下程式碼建立了一個代表 GBK 編碼的 Charset
物件
Charset charset = Charset.forName("GBK");
Charset
類還有一個靜態方法 defaultCharset()
,它返回代表本地平臺的預設字元編碼的 Charset
物件
通道 Channel
通道(Channel)用來連線緩衝區與資料來源或資料匯(即資料目的地),資料來源的資料經過通道到達緩衝區,緩衝區的資料經過通道到達資料匯
Channel 的主要層次結構如下:
java.nio.channels.Channel
介面只宣告瞭兩個方法:
// 關閉通道
close();
// 判斷通道是否開啟
isOpen();
Channel
介面的兩個最重要的子介面是 ReadableByteChannel
和 WritableByteChannel
。ReadableByteChannel
介面宣告瞭 read(ByteBuffer dst)
方法,該方法把資料來源的資料讀入引數指定的 ByteBuffer
緩衝區中。WritableByteChannel
介面宣告瞭 write(ByteBuffer src)
方法,該方法把引數指定的 ByteBuffer
緩衝區中的資料寫到資料匯中
ByteChannel
介面是一個便利介面,它擴充套件了 ReadableByteChannel
和 WritableByteChannel
介面,因而同時支援讀寫操作
ScatteringByteChannel
介面擴充套件了 ReadableByteChannel
介面,允許分散地讀取資料。分散讀取資料指單個讀取操作能填充多個緩衝區,ScatteringByteChannel
介面宣告瞭 read(ByteBuffer[] dsts)
方法,該方法把從資料來源讀取的資料依次填充到引數指定的各個 ByteBuffer
GatheringByteChannel
擴充套件了 WritableByteChannel
介面,允許集中地寫入資料。集中寫入資料指單個寫操作能把多個緩衝區的資料寫到資料, GatheringByteChannel
介面宣告瞭 write(ByteBuffer[] srcs)
方法,該方法依次把引數指定的每個 ByteBuffer
中的數寫到資料匯
FileChannel
類是 Channel
介面的實現類,代表一個與檔案相連的通道。該類實現了 ByteChannel
、ScatteringByteChannel
和 GatheringByteChannel
介面,支援讀操作、寫操作、分散讀操作和集中寫操作。FileChannel
類沒有提供公開的構造方法,因此不能用 new
語句來構造它的例項。不過,在FileInputStream
、FileOutputStream
和 RandomAccessFile
類中提供了 getChannel()
方法,該方法返回相應的 FileChannel
物件
SelectableChannel
也是一種通道,它不僅支援阻塞的 I/O
操作,還支援非阻塞的 I/O
。SelectableChannel
有兩個子類,ServerSocketChannel
和 SocketChannel
。SocketChannel
還實現了 ByteChannel
介面,具有 read(ByteBuffer dst)
和 write(ByteBuffer src)
方法
1. SelectableChannel 類
SelectableChannel
是一種支援阻塞 IO 和非阻塞 IO 的通道。在非阻塞模式下,讀寫資料不會阻塞,並且 SelectableChannel
可以向 Selector
註冊讀就緒和寫就緒等事件。Selector
負責監控這些事件,等到事件發生時,比如發生了讀就緒事件,SelectableChannel
就可以執行讀操作了
SelectableChannel
的主要方法如下:
// 當引數block為true,表示把SelectableChannel設為阻塞模式
// 當引數block為false時,表示把SelectableChannel設為非阻塞模式
// SelectableChannel預設採用阻塞模式
// 該方法返回SelectableChannel物件本身的引用,相當於return this
public SelectableChannel configureBlocking(boolean block) throws IOException
// 以下兩個方法都向Selector註冊事件
public SelectionKey register(Selector sel,int ops) throws ClosedChannelException
public SelectionKey register(Selector sel,int ops,Object attachment) throws ClosedChannelException
以下是 socketChannel
向 Selector
註冊讀就緒和寫就緒事件
SelectionKey key = socketChannel.register(selector.SelectionKey.OP_READ | SelectionKey.OP_WRITE);
register()
方法返回一個 SelectionKey
物件,SeletionKey
被用來跟蹤被註冊的事件。第二個 register()
方法還有一個 Object
型別的引數 attachment
,用於為 SelectionKey
關聯附件,當被註冊事件發生後,需要處理該事件時,可以從 SelectionKey
中獲得這個附件,該附件可用來包含與處理這個事件相關的資訊
2. ServerSocketChannel 類
ServerSocketChannel
繼承自 SelectableChannel
,是 ServerSocket
的替代類,透過它的靜態方法 open()
來建立。每個 ServerSockeChannel
物件都與一個 ServerSocket
物件關,透過 socket()
方法返回與它關聯的 ServerSocket
物件。可透過以下方式把伺服器程式繫結到一個本地埠:
serverSocketChannel.socket().bind(port);
ServerSocketChannel
的主要方法如下:
// 返回一個ServerSocketChannel物件,該物件沒有與任何本地埠繫結,並且處於阻塞模式
public static ServerSocketChannel open() throws IOException
// 用於接收客戶的連線,如果處於非阻塞狀態,當沒有客戶連線時就立即返回null
public SocketChannel accept() throws IOException
// 返回ServerSocketChannel所能產生的事件,這個方法總是返回SelectionKey.OP_ACCEPT
public final int validOps()
// 返回ServerSocketChannel關聯的ServerSocket物件
public ServerSocket socket()
3. SocketChannel類
SockeChannel
可以被看作是 Socket
的替代類,SockeChannel
不僅繼承了 SelectableChannel
,而且實現了 ByteChannel
。SockeChannel
同樣透過它的靜態方法 open()
來建立
public static SocketChannel open() throws IOException
// 帶引數的構造方法還會建立與遠端伺服器的連線
public static SocketChannel open(SocketAddress remote) throws IOException
SocketChannel
的主要方法如下:
// 返回ServerSocketChannel所能產生的事件,這個方法總是返回以下值
// SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE
public final int validOps()
// 返回SocketChannel關聯的Socket物件
public Socket socket()
// 建立遠端連線,當處於非阻塞模式時,如果立即連線成功返回true,否則返回false
public boolean connect(SocketAddress remote) throws IOException
// 判斷底層Socket是否已經建立遠端連線
public boolean isConnected()
// 判斷是否正在進行遠端連線,如果遠端連線操作已經開始,但還沒有完成,則返回true,否則返回false
// 也就是說,無論底層Socket還沒有開始連線,或者已經連線成功,該方法都會返回false
public boolean isConnectionPending()
// 試圖完成連線遠端伺服器的操作
// 非阻塞模式下,建立連線從呼叫connect()方法開始,到呼叫finishConnect()方法結束
// 如果在呼叫此方法之前連線已經建立,則立即返回true,否則立即返回false
// 阻塞模式下,如果連線操作還沒有完成,則會進入阻塞狀態,直到連線完成,或者出現IO異常
public boolean finishConnect) throws IOException
// 從Channel讀入若干位元組,存放到引數指定的ByteBuffer
// 假設ByteBuffer剩餘容量為r,阻塞模式下,該方法會爭取讀到r位元組
// 如果輸入流中不足r位元組,就進入阻塞狀態,直到讀入了r位元組,或者讀到了輸入流末尾,或者出現了IO異常
// 非阻塞模式下,該方法奉行能讀到多少資料就讀多少資料的原則
// 通道中的可讀資料,有可能不足r位元組,或者為0位元組,總是立即返回
// 該方法返回實際上讀入的位元組數,有可能為0,如果返回-1,表示讀到了輸入流的末尾
public int read(ByteBuffer dst) throws IOException
// 把引數src指定的ByteBuffer的位元組寫到Channel
// 假設ByteBuffer剩餘容量為r,阻塞模式下,該方法會爭取輸出r位元組
// 如果底層網路的輸出緩衝區不能容納r位元組,就進入阻塞狀態,直到輸出了r位元組,或者出現了IO異常
// 非阻塞模式下,該方法奉行能輸出多少資料就輸出多少資料的原則,有可能不足r位元組,或者為0位元組,總是立即返回
// 該方法返回實際上輸出的位元組,有可能為0
public int write(ByteBuffer src) throws IOException
Selector 類
只要 ServerSockerChannel
以及 SocketChannel
向 Selector
註冊了特定的事件,Selector
就會監控這些事件是否發生。SelectableChannel
的 register()
方法負責註冊事件,該方法返回 SelectionKey
物件,該物件是用於跟蹤這些被註冊事件的控制程式碼
Selector
物件中會包含三種型別的 SelectionKey
的集合:
all-keys
:當前所有向Selector
註冊的SelectionKey
的集合,Selector
的keys()
方
法返回該集合selected-keys
:相關事件已經被Selector
捕獲的SelectionKey
的集合,Selector
的selectedKeys()
方法返回該集合cancelled-keys
:已經被取消的SelectionKey
的集合,Selector
沒有提供訪問這
種集合的方法
當執行 SelectableChannel
的 registe()
方法,會新建一個 SelectionKey
並加入 Selector
的 all-keys
集合中。如果關閉了與 SelectionKey
物件關聯的 Channel
物件,或者呼叫了 SelectionKey
物件的 cancel()
方法,那麼這個 SelectionKey
物件就會被加入 cancelled-keys
集合,表示已經被取消,在程式下一次執行 Selector
的 select()
方法時,被取消的 SelectionKey
物件將從所有的集合(包括 all-keys
集合、selected-keys
集合和 cancelled-keys
集合)中被刪除
在執行 Selector
的 select()
方法時,如果與 SelectionKey
相關的事件發生了,這個 SelectionKey
就被加入 selected-keys
集合中。程式直接呼叫 selected-keys
集合的 remove()
方法,或者呼叫它的 Iterator
的 remove()
方法,都可以從 selected-keys
集合中刪除一個 SelectionKey
物件
程式不允許直接透過集合介面的 remove()
方法刪除 all-keys
集合中的 SelectionKey
物件,這會導致 UnsupportedOperationException
Selector
類的主要方法如下:
// Selector的靜態工廠方法,建立一個Selector物件
public static Selector open() throws IOException
// 判斷Selector是否處於開啟狀態,Selector物件建立後就處於開啟狀態,當呼叫close()方法就進入關閉狀態
public boolean isOpen()
// 返回Seleclor的all-keys集合,包含了所有與Seclector關聯的SelectionKey物件
public Set<SelectionKey> keys()
// 返回相關事件已經發生的SelectionKey物件的數目
// 該方法採用非阻塞的工作方式,返回當前相關事件已經發生的SelectionKey物件的數目,如果沒有,就立即返回0
public int selectNow() throws IOException
// 返回相關事件已經發生的SelectionKey物件的數目
// 該方法採用阻塞的工作方式,如果一個也沒有,就進入阻塞狀態,直到出現以下情況之一,就會從select()返回:
// 1.至少有一個SelectionKey的相關事件已經發生
// 2.其他執行緒呼叫了Selector的wakeup()方法
// 3.當前執行select()方法的執行緒被其他執行緒中斷
// 4.超出了等待時間
public int select() throws IOException
public int select(long timeout) throws IOException
// 喚醒執行Selector的select()方法
public Selector wakeup()
// 關閉 Selector
// 如果有其他執行緒正執行這個Selector的select()方法並且處於阻塞狀態,這個執行緒會立即返回
// close()方法使得Selector佔用的所有資源都被釋敗,所有關聯的SelectionKey都被取消
public void close() throws IOException
SelectionKey 類
SelectionKey
中定義了四種事件,分別用四個 int 型別的常量來表示:
SelectionKey.OP_ACCEPT
:接收連線就緒事件,表示伺服器監聽到了客戶連線,伺服器可以接收這個連線了,常量值為 16SeiectionKey.OP_CONNECT
:連線就緒事件表示客戶與伺服器的連線已經建立成功,常量值為 8SelectionKey.OP_READ
:讀就緒事件,表示通道中已經有了可讀資料,可以執行讀操作了,常量值為 1SelectionKey.OP_WRITE
:寫就緒事件表示已經可以向通道寫資料了,常量值為 4
以上常量分別佔據不同的二進位制位,因此可以透過二進位制的或運算來將它們進行任意組合
一個 SelectionKey
物件中包含兩種型別的事件:
-
所有感興趣的事件:透過
SelectableChannel
的register()
方法註冊事件時,可以在引數中指定SelectionKey
感興趣的事件SelectionKey key = socketChannel.register(selector,SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
該程式碼表示這個
SelectionKey
對讀就緒和寫就緒事件感興趣,與之關聯的Selector
物件會負責監控這些事件SelectionKey
的帶引數的interestOps(int ops)
方法也可以為SelectionKey
物件增加一個感興趣的事件,如下程式碼所示:key.interestOps(SelectionKey.OP_WRITE);
-
所有已經發生的事件:
SeletionKey
的readyOps()
方法返回所有已經發生的事件,例如假定返回值為SelectionKey.OP_WRITE | SelectionKey.OP_READ
,表示讀就緒和寫就緒事件已經發生了,這意味著與之關聯的SocketChannel
物件可以進行讀操作和寫操作了
SelectionKey
的主要方法如下:
// 返回與這個SelectionKey物件關聯的SelectableChannel物件
public SelectableChannel channel()
// 返回與這個SelectionKey物件關聯的Selector物件
public Selector selector()
// 判斷這個SelectionKey是否有效
// 當SelectionKey物件建立後,它就一直處於有效狀態
// 如果呼叫了它的cancel()方法,或關閉了與它關聯的SelectableChannel或Selector物件,它就失效
public boolean isValid()
// 使SelectionKey物件失效
public void cancel()
// 返回這個SelectionKey感興趣的事件
public int interestOps()
// 為SelectionKey增加感興趣的事件
public SelectionKey interestOps(int ops)
// 返回已經就緒的事件
public int readyOps()
// 判斯與之關聯的SocketChannel的讀就緒事件是否已經發生
public final boolean isReadable()
// 判斷與之關聯的SocketChannel的寫就緒事件是否已經發生
public final boolean isWritable()
// 判斷與之關聯的SocketChannel的連線就緒事件是否已經發生
public final boolean isConnectable()
// 判斷與之關聯的ServerSocketChannel的接收連線就緒事件是否已經發生
public final boolean isAcceptable()
// 使SelectionKey關聯一個附件,一個SelectionKey物件只能關聯一個Object型別的附件
// 如果多次呼叫該方法,則只有最後一個附件與SelectionKey物件關聯
public final Object attach(Object obj)
// 返回與SelectionKey物件關聯的附件
public final Object attachment()
Channels 類
Channels
類是一個簡單的工具類,提供了通道與傳統的基於 IO 的流、Reader
和 Writer
之間進行轉換的靜態方法
ReadableByteChannel newChannel(InputStream in) // 輸入流轉換成讀通道
WritableByteChannel newChannel(OutputStream out) // 輸出流轉換成寫通道
InputStream newInputStream(AsynchronousByteChannel ch) // 非同步通道轉換成輸入流
InputStream newInputStream(ReadableByteChannel ch) // 讀通道轉換成輸入流
OutputStream newOutputStream(AsynchronousByteChannel ch) // 非同步通道轉換成輸出流
OutputStream newOutputStream(WritableByteChannel ch) // 寫通道轉換成輸出流
Reader newReader(ReadableByteChannel ch,String csName) // 讀通道轉換成Reader,引數csName指定字元編碼
Reader newReader(ReadableByteChannel ch,Charset charset)//讀通道轉換成Reader.引數charset指定字元編碼
Reader newReader(ReadableByteChannel ch,CharsetDecoder dec, int minBufferCap) // 讀通道轉換成 Reader,引數dec指定字元解碼器,引數minBufferCap指定內部位元組緩衝區的最小容量
Writer newWriter(WritableByeChannel ch, String csName) // 寫通道轉換Writer.引數csName指定字元編碼
Writer newWriter(WritableByeChannel ch, Charset charset) // / 寫通道轉換Writer.引數charset指定字元編碼
Writer newWriter(WritableByeChannel ch, CharsetEncoder enc, int minBufferCap) // 寫通道轉換成Writer,引數dec指定字元解碼器,引數minBufferCap指定內部位元組緩衝區的最小容量
Socket 選項
從 JDK7 開始,SocketChannel
、ServerSocketChannel
、AsynchronousSocketChannel
、AsynchronousServerSocketChannel
和 DatagramChannel
都實現了新的 NetworkChannel
介面。NetworkChannel
介面的主要作用是設定和讀取各種 Socket 選項
NetworkChannel
介面提供了用於設定和讀取這些選項的方法:
<T> T getOption(SocketOption<T> name) // 獲取特定的Socket選項值
<T> NetworkChannel setOption(SocketOption<T> name, T value) // 設定特定的Socket選項
Set<SocketOption<?>> supportedOptions() // 獲取所有支援的Socket選項
SocketOptionl
類是一個泛型類,SocketOption<T>
中的 T
代表特定選項的取值型別,可選值包括 Integer
、Boolean
和 NetworkInterface
StandardSocketOptions
類提供了以下表示特定選項的常量:
SocketOption<NetworkInterface> -- StandardSocketOptions.IP_MULTICAST_IF
SocketOption<Boolean> -- StandardSocketOptions.IP_MULTICAST_LOOP
SocketOption<Integer> -- StandardSocketOptions.IP_MULTICAST_TTL
SocketOption<Integer> -- StandardSocketOptions.IP_TOS
SocketOption<Boolean> -- StandardSocketOptions.SO_BROADCAST
SocketOption<Boolean> -- StandardSocketOptions.SO_KEEPALIVE
SocketOption<Integer> -- StandardSocketOptions.SO_LINGER
SocketOption<Integer> -- StandardSocketOptions.SO_RCVBUF
SocketOption<Boolean> -- StandardSocketOptions.SO_REUSEADDR
SocketOption<Boolean> -- StandardSocketOptions.SO_REUSEPORT
SocketOption<Integer> -- StandardSocketOptions.SO_SNDBUF
SocketOption<Boolean> -- StandardSocketOptions.TCP_NODELAY