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()方法,唤醒阻塞的线程。