首先,netty的处理模型是存在一组IO线程,去处理IO事件,如read,connect,write等等,对于服务端接收到的每个channel,都会将该channel映射到一条IO线程。当一个channel被建立之后,需要将其初始化,包含给他创建pipleline并填充channelhandler;给channel附以channelOptions和channelAttrs等,其中填充channelhandler可能会导致问题
如某个channelHandler是共享的,却被添加到多个channel上,而每个channel对应一个线程,必然会导致线程并发问题,如下列代码:
public final class Server {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
UnSafeChannelHandler handler=new UnSafeChannelHander()
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, true)
.childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(handler);
}
});
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
此时你需要自己实现channelHandler的线程安全性,如使用线程安全类或者加锁等,但是这样的操作可能会阻塞IO线程,必须慎用!。(用于全局性的处理,如网络监控,性能检测等)
不仅如此,上述代码都可能不会运行成功,看看netty为channel赋channelHandler的源码实现:
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}
要求是被Sharable注解的共享channelHandler才能被添加至不同的channel中,大家可以看看ChannelInitializer,他就是Shareable的;
另外业务线程也有可能并发调用IO方法如read,write等等,这样会出现线程安全问题吗?
执行IO任务时会转回为IO线程,也就是NioEventLoop,他会将IO方法分装成一个task,串行执行。因此此类方法不会产生并发问题。