Selector.select()無法正确回調的問題
我們都知道socketChannel通過register方法注冊到Selector上後,Selector.select()會阻塞住,直到監聽的事件發生後,select()會通過Linux的epoll方法回調,并喚醒該線程。
現在有這麼一個疑問,如果開一個線程去執行Selector.select()方法,當監聽的事件發生後,該線程是否會被喚醒?
以ServerSocketChannel監聽accept事件為例,這裡開了一個Reactor線程來執行Selector.select():
private static class Reactor extends Thread{
@Override
public void run() {
try {
select();
} catch (IOException e) {
e.printStackTrace();
}
}
private void select() throws IOException{
while (true){
int select = selector.select();
if (select == 0) continue;
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isAcceptable()){
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
System.out.println("接收到請求");
}
iterator.remove();
}
}
}
而在主線程中,如果先開啟這個Reactor線程,再去接收連接配接:
public static void main(String[] args) {
try {
new Reactor().start();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(8080);
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(address);
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
然後開一個用戶端去連接配接該服務端,結果發現并沒有輸出,Reactor線程一直阻塞在selector.select()上;
但是如果先去接收請求再開啟這個Reactor線程:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(8080);
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(address);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
new Reactor().start();
結果發現會輸出“接收到請求”,這是因為此時Reactor線程在執行selector.select()時由于有已就緒的,是以不會阻塞而繼續往下執行。
然後再開一個用戶端去連接配接該服務端,發現此時能夠正常輸出“接收到請求”。
這說明了Reactor線程在第一次執行selector.select()時,必須要有已就緒的事件,否則後面即使有了已就緒的事件,還是會阻塞在selector.select()上。
在測試監聽SocketChannel時,同樣也發現了這一問題。
這時如果想要正确地回調,可以使用select(time),但這樣會造成CPU浪費;也可以在接收到第一個連接配接時,使用select.wakeup()方法,喚醒阻塞的線程。