1. 程式人生 > >NIO 之 Selector實現原理

NIO 之 Selector實現原理

相關文章

概述

Selector允許單執行緒處理多個 Channel。如果你的應用打開了多個連線(通道),但每個連線的流量都很低,使用Selector就會很方便。例如,在一個聊天伺服器中。

這是在一個單執行緒中使用一個Selector處理3個Channel的圖示:
selector與channel關係
要使用Selector,得向Selector註冊Channel,然後呼叫它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,執行緒就可以處理這些事件,事件的例子有如新連線進來,資料接收等。

Selector 作用

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

Selector 原始碼分析

public abstract class Selector implements Closeable {
    protected Selector() { }
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }
    public abstract boolean isOpen();
    public abstract SelectorProvider provider
(); public abstract Set<SelectionKey> keys(); public abstract Set<SelectionKey> selectedKeys(); public abstract int selectNow() throws IOException; public abstract int select(long timeout) throws IOException; public abstract int select() throws IOException; public
abstract Selector wakeup(); public abstract void close() throws IOException;

Selector 是個抽象類,提供一個靜態的方法獲取Selector子類SelectorImpl的例項。

下面分析Selector的幾個方法

register 方法

該方法是在 Channel的register方法中呼叫的。具體詳見NIO 之 Channel實現原理
register 方法
1. 通過channel和selector構造一個SelectionKey的例項。
2. SelectionKey 註冊感興趣的事件

這四種事件用SelectionKey的四個常量來表示:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

select 方法

不同的 Channel 註冊到 Selector 後,就可以隨時查詢 Selector ,找出哪些 Channel 已經準備好可以進行處理。Channel 可能準備好上面註冊到 Selector 感興趣事件中的一個或多個。

  1. select()
    獲取就緒的 Channel,阻塞方法,沒有就緒的 Channel 就一直阻塞該執行緒。
public int select() throws IOException {
    return select(0);
}
  1. select(long timeout)
    獲取就緒的 Channel, 阻塞方法,阻塞 timeout 時間,如果超時還沒有就緒的 Channel,返回0,不做任何操作。
public int select(long timeout)
        throws IOException
    {
        if (timeout < 0)
            throw new IllegalArgumentException("Negative timeout");
        return lockAndDoSelect((timeout == 0) ? -1 : timeout);
    }
  1. selectNow()
    獲取就緒的 Channel,如果沒有就緒的就直接返回,不阻塞當前執行緒。
public int selectNow() throws IOException {
        return lockAndDoSelect(0);
    }

上面三個 select方法底層都是呼叫 lockAndDoSelect 方法。
lockAndDoSelect方法的引數值 說明:
-1 : 一直阻塞,直到有就緒的 Channel 可處理
0 : 不阻塞
0: 表示阻塞多長時間

keys 方法

獲取所有註冊到 Selector 上的 SelectionKey
 public Set<SelectionKey> keys() {
    if (!isOpen() && !Util.atBugLevel("1.4")) throw new ClosedSelectorException();
        return publicKeys;
}

selectedKeys 方法

獲取所有註冊到 Selector 上就緒 Channel 的 SelectionKey 資訊。

public Set<SelectionKey> selectedKeys() {
    if (!isOpen() && !Util.atBugLevel("1.4")) throw new ClosedSelectorException();
        return publicSelectedKeys;
}

SelectionKey 解析

SelectionKey 類結構如下:

public abstract class SelectionKey {
    protected SelectionKey() { }
    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;
    //附件資訊
    private volatile Object attachment = null;
    ....
}
  • public abstract SelectableChannel channel()
    獲取channel物件
  • public abstract Selector selector()
    獲取seletor物件

  • public abstract void cancel()
    從 Selector 中取消註冊該Channel

  • public abstract int interestOps()
    獲取該chennel 註冊到 selector 上的事件

  • public abstract SelectionKey interestOps(int ops)
    修改註冊到 selector 上的事件

  • public abstract int readyOps()
    是否讀就緒

    讀就緒不等於可讀,如果沒有註冊讀事件是不能讀的。

  • public final boolean isReadable()
    判斷是否可讀

  • public final boolean isWritable()
    是否可寫

  • public final boolean isConnectable()
    是否已經連線

  • public final Object attach(Object ob)
    新增附件資訊

  • public final Object attachment()
    獲取附件資訊