天天看點

Selector.select()無法正确回調的問題Selector.select()無法正确回調的問題

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