再版: 同時プログラミング ネットワーク: ifeve.com NIO チュートリアル
セレクター (セレクター) は、1 つ以上の NIO チャネルを検出し、そのチャネルが読み取りや書き込みなどのイベントの準備ができているかどうかを知ることができる Java NIO のコンポーネントです。このようにして、単一のスレッドで複数のチャネルを管理できるため、複数のネットワーク接続を管理できます。
単一のスレッドのみを使用して複数のチャネルを処理する利点は、チャネルの処理に必要なスレッドが少なくなることです。実際、1 つのスレッドだけを使用してすべてのチャネルを処理することも可能です。オペレーティング システムの場合、スレッド間のコンテキストの切り替えは非常にコストがかかり、各スレッドは一部のシステム リソース (メモリなど) を占有します。したがって、使用するスレッドは少ないほど良いのです。
ただし、最新のオペレーティング システムと CPU はマルチタスク処理においてますます優れているため、マルチスレッドのオーバーヘッドは時間の経過とともにますます小さくなっているということを覚えておく必要があります。実際、CPU に複数のコアがある場合、マルチタスクを使用しないと CPU パワーが無駄になる可能性があります。いずれにせよ、そのデザインについての議論は別の記事で行う必要があります。ここでは、Selector を使用して複数のチャネルを処理できることを知っていれば十分です。
次のように、Selector.open() メソッドを呼び出してセレクターを作成します。
Selector selector = Selector.open();
channel.configureBlocking(false); SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
Selector とともに使用する場合、チャネルはノンブロッキング モードである必要があります。これは、FileChannel を非ブロッキング モードに切り替えることができないため、FileChannel をセレクターで使用できないことを意味します。ソケットチャネルは問題ありません。
register() メソッドの 2 番目のパラメータに注意してください。これは「興味のあるコレクション」であり、セレクターを通じてチャンネルを聞いているときにどのようなイベントに興味があるかを意味します。 4 つの異なるタイプのイベントをリッスンできます:
これら 4 つのイベントは、SelectionKey の 4 つの定数で表されます:
SelectionKey.OP_WRITE
あなたが正しいなら複数のイベントに興味がある場合は、次のように「ビット OR」演算子を使用して定数を接続できます:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
4. SelectionKey
前のセクションでは、セレクターにチャンネルを登録するときに、 register() メソッドは SelectionKey オブジェクトを返します。このオブジェクトには、興味を引くかもしれないいくつかのプロパティが含まれています:
interest collection
ready collection
Channel
Selector
追加のオブジェクト (オプション)
これらのプロパティは次のとおりです説明された。
「セレクターを使用したチャンネルの登録」セクションで説明したように、インタレスト コレクションは、選択した興味深いイベントのコレクションです。次のように、SelectionKey を通じてインタレスト コレクションの読み取りと書き込みができます:
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
ready collection
ready collection は、チャネルの準備ができている操作のコレクションです。選択 (Selection) 後、まずレディ セットにアクセスします。選択については次のセクションで説明します。次のように準備完了コレクションにアクセスできます:
int readySet = selectionKey.readyOps();
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();
Channel + Selector
SelectionKey から Channel と Selector にアクセスするのは簡単です。以下のように:
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();
特定のチャンネルを簡単に識別できるように、SelectionKey にオブジェクトまたは詳細情報を添付できます。たとえば、チャネルで使用するバッファや、集約されたデータを含むオブジェクトをアタッチできます。使用方法は次のとおりです。
selectionKey.attach(theObject); Object attachedObj = selectionKey.attachment();
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
5. Selector を使用してチャネルを選択します
1 つ以上のチャネルが Selector に登録されたら、いくつかのオーバーロードされた 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()方法调用之间,只有一个通道就绪了。
一旦调用了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等。
某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。
如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。
用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。
以上がJAVA-5NIOのセレクタの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。