首頁  >  文章  >  Java  >  JAVA-5NIO之Selector

JAVA-5NIO之Selector

巴扎黑
巴扎黑原創
2017-06-26 09:30:541096瀏覽

轉載:並發程式設計網:ifeve.com NIO教學

Selector(選擇器)是Java NIO中能夠偵測一到多個NIO通道,並能夠知曉通道是否為諸如讀寫事件做好準備的組件。這樣,一個單獨的執行緒可以管理多個channel,從而管理多個網路連線。

一、為什麼使用Selector?

只用單一執行緒來處理多個Channels的好處是,只需要更少的執行緒來處理通道。事實上,可以只用一個執行緒處理所有的通道。對於作業系統來說,執行緒之間上下文切換的開銷很大,而且每個執行緒都要佔用系統的一些資源(如記憶體)。因此,使用的線程越少越好。

但是,需要記住,現代的作業系統和CPU在多任務方面表現的越來越好,所以多執行緒的開銷隨著時間的推移,變得越來越小了。實際上,如果一個CPU有多個內核,不使用多任務可能是在浪費CPU能力。不管怎麼說,關於那種設計的討論應該放在另一篇不同的文章中。在這裡,只要知道使用Selector能夠處理多個通道就足夠了。

二、Selector的建立

透過呼叫Selector.open()方法建立一個Selector,如下:

Selector selector = Selector.open();

三、向Selector註冊通道

#為了將Channel和Selector配合使用,必須將channel註冊到selector上。透過SelectableChannel.register()方法來實現,如下:

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,
	Selectionkey.OP_READ);

與Selector一起使用時,Channel必須處於非阻塞模式下。這表示不能將FileChannel與Selector一起使用,因為FileChannel不能切換到非阻塞模式。而套接字通道都可以。

注意register()方法的第二個參數。這是一個“interest集合”,意思是在透過Selector監聽Channel時對什麼事件感興趣。可以監聽四種不同類型的事件:

  1. Connect

  2. #Accept

  3. Read

  4. Write

通道觸發了一個事件意思是該事件已經就緒。所以,某個channel成功連接到另一個伺服器稱為「連線就緒」。一個server socket channel準備好接收新進入的連線稱為「接收就緒」。一個有資料可讀的通道可以說是「讀就緒」。等待寫資料的通道可以說是「寫就緒」。

這四個事件以SelectionKey的四個常數來表示:

  1. SelectionKey.OP_CONNECT

  2. SelectionKey.OP_ACCEPT

  3. SelectionKey.OP_READ

  4. #SelectionKey.OP_WRITE

如果你對不只一種事件感興趣,那麼可以用「位元或」運算元將常數連接起來,如下:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

在下面還會繼續提到interest集合。

四、SelectionKey

在上一小節中,當向Selector註冊Channel時,register()方法會傳回一個SelectionKey物件。這個物件包含了一些你感興趣的屬性:

  • interest集合

  • #ready集合

  • Channel

  • Selector

  • 附加的物件(可選)

下面我會描述這些屬性。

interest集合

就像是向Selector註冊頻道一節中所描述的,interest集合是你所選擇的感興趣的事件集合。可以透過SelectionKey讀取和寫入interest集合,像這樣:

int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;

可以看到,用「位元與」操作interest 集合和給定的SelectionKey常數,可以確定某個確定的事件是否在interest 集合中。

ready集合

ready 集合是通道已經準備就緒的操作的集合。在一次選擇(Selection)之後,你會先造訪這個ready set。 Selection將在下一小節進行解釋。可以這樣存取ready集合:

int readySet = selectionKey.readyOps();

可以用像偵測interest集合那樣的方法,來偵測channel中什麼事件或操作已經就緒。但是,也可以使用以下四個方法,它們都會傳回一個布林類型:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

Channel + Selector

從SelectionKey存取Channel和Selector很簡單。如下:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

附加的物件

可以將物件或更多資訊附著在SelectionKey上,這樣就能方便的辨識某個給定的頻道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集資料的某個物件。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

還可以在用register()方法向Selector註冊Channel的時候附加物件。如:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

 五、透過Selector選擇通道

一旦向Selector註冊了一或多個通道,就可以呼叫幾個重載的select()方法。這些方法會傳回你所感興趣的事件(如連接、接受、讀取或寫入)已經準備就緒的那些通道。換句話說,如果你對「讀取就緒」的通道感興趣,select()方法會傳回那些讀事件已經就緒的通道。

下面是select()方法:

  • int select()

  • int select(long timeout)

  • int selectNow()

select()阻塞到至少有一个通道在你注册的事件上就绪了。

select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。

selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。

select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。

selectedKeys()

一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示:

Set selectedKeys = selector.selectedKeys();

当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。

  /** * selector     */@Testpublic void test3() throws IOException {
        Selector selector = Selector.open();
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);//向selector注册此通道SelectionKey register = socketChannel.register(selector, SelectionKey.OP_READ);//在本例,i == SelectionKey.OP_READint i = register.interestOps();//所以判断 可以通过这样来判断事件boolean b = (i & SelectionKey.OP_READ) == SelectionKey.OP_READ;//当然也可以通过 isXX方法来判断boolean readable = register.isReadable();//返回已经准备好的 SelectionKey数量,如果>0,表示有了,就可以调用下面的方法了int select = selector.select();/** * if select > 0,一般是 while(true)循环         *///这里面保存着已经 准备好的 SelectionKey,也就是通道//注意 这里面的 SelectionKey需要手动移除,不会自动移除Set7242e6f37ad3976ef4c41538eb6faedf selectionKeys = selector.selectedKeys();

        Iterator keyIterator = selectionKeys.iterator();while(keyIterator.hasNext()) {
            SelectionKey key = (SelectionKey) keyIterator.next();//获取通道SelectableChannel channel = key.channel();//获取selectorSelector selector1 = key.selector();if(key.isAcceptable()) {// a connection was accepted by a ServerSocketChannel.} else if (key.isConnectable()) {// a connection was established with a remote server.} else if (key.isReadable()) {// a channel is ready for reading} else if (key.isWritable()) {// a channel is ready for writing            }//移除            keyIterator.remove();
        }//关闭        selector.close();
    }

 

注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。

wakeUp()

某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。

如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。

close()

用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

 

 

以上是JAVA-5NIO之Selector的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn