开启掘金成长之旅!这是我参与「掘金日新计划 · 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方法的处理策略:
- 如果是首次运行
- 初始化线程并启动,即调用run()方法,此时在select()处阻塞
- 初始化selector
- 让selector监听传入channel的事件,将其作为任务传入队列中
- 调用wakeup方法唤醒selector,让子线程的select()方法停止阻塞继续运行
- 子线程继续运行run()方法
- 将队列中的任务取出来运行,该任务是注册读事件
- 处理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();
}
}
}
}