多线程Worker的NIO方式

36 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

Boss线程只处理连接事件,Worker线程处理读写事件,因此对于Boss来说它的工作是启动Worker线程,在监听到连接事件后将其SocketChannel注册到Worker线程上,Worker线程的工作是当有读写事件发生后进行读写事件的处理

Worker worker = new Worker("worker-0");
worker.register(); // 1. 启动Worker的线程并调用select()方法,阻塞
while (true) {
    boss.select(); // 2. boss主线程接收了连接
    Iterator<SelectionKey> iterator = boss.selectedKeys().iterator();
    while (iterator.hasNext()) {
        SelectionKey key = iterator.next();
        iterator.remove();
        if (key.isAcceptable()) {
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            // 3. 将SocketChannel注册到Selector上并对读事件关注
            sc.register(worker.getSelector(), SelectionKey.OP_READ, null);
        }
    }
}

上述代码的问题在于:由于第1步中worker线程阻塞,在第3步无法顺利执行register方法。

解决该问题的方法是重点在于避免select阻塞了注册关注事件的动作,因此要放到一个线程中运行来保证其运行的先后顺序,修改Worker代码如下,在其内部设置一个队列用于存放“注册对读事件感兴趣”的任务,在子线程运行时从队列中取出运行。

register方法的处理策略:

  1. 如果是首次运行
    1. 初始化线程并启动,即调用run()方法,此时在select()处阻塞
    2. 初始化selector
  2. 让selector监听传入channel的事件,将其作为任务传入队列中
  3. 调用wakeup方法唤醒selector,让子线程的select()方法停止阻塞继续运行
  4. 子线程继续运行run()方法
    1. 将队列中的任务取出来运行,该任务是注册读事件
    2. 处理selector上的其他事件
public class Worker implements Runnable {

    private Thread thread;

    private Selector selector;

    private String name;

    private ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();

    /**
     * 防止重复初始化
     */
    private volatile boolean start = false;

    public Worker(String name) {
        this.name = name;
    }


    public void register(SocketChannel sc) throws IOException {
        if (!start) {
            thread = new Thread(this, name);
            thread.start();
            selector = Selector.open();
            start = true;
        }
        queue.add(() -> {
            try {
                sc.register(selector, SelectionKey.OP_READ, null);
            } catch (ClosedChannelException e) {
                e.printStackTrace();
            }
        });
        selector.wakeup();


    }

    @Override
    public void run() {
        while (true) {
            try {
                selector.select();
                Runnable task = queue.poll();
                if (Objects.nonNull(task)) {
                    // 执行注册
                    task.run();
                }
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (key.isReadable()) {

                    } else if (key.isWritable()) {

                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}