netty的@ChannelHandler.Sharable
一直以来,我都以为netty的channelHandler只要加上@ChannelHandler.Sharable注解,他在整个生命周期中就是以单例的形式存在了,直到今天,我想知道到底究竟是不是单例存在的。于是,有了下面的经历,不得不说,搜了好多篇博客,感觉都是照搬乱套,毫无章法可言。
需求
添加一个StatusHandler,目的为了记录同时在线的设备数量
代码
@ChannelHandler.Sharable
public class StatusHandler extends ChannelInboundHandlerAdapter {
private volatile static int count = 0;
private Object lock = new Object();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
synchronized (lock) {
count++;
}
System.out.println(getClass().toGenericString() + ":" + this);
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
synchronized (lock) {
count--;
}
super.channelInactive(ctx);
}
public static int getCount() {
return count;
}
}
问题现象
使用jmeter模拟了10000个设备,结果设备树总是不到10000,很明显了,同步锁压根没有起到作用。代码那么简单,肯定不会错的吧。于是想到,可能这货不是单例。于是,简单的 System.out.println(getClass().toGenericString() + ":" + this);以下,果然,每次的地址都是不一样的。为什么呐?然后百度了一下,没搞明白。智能硬着头皮看源码了。
此时的初始化的handler
明白人已经看明白了,为什么每次都新建一个对象。(代码2)
public class InitHandler extends ChannelInitializer<NioSocketChannel> {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
System.out.println(this);
nioSocketChannel.pipeline()
.addLast(new TimeoutHandler(5,1000,1000))
.addLast(new MessageHandler())
.addLast(new StatusHandler())
.addLast(new WriteHandler<String>());
}
}
原因分析
可以确定的是,一个连接新建立的时候首先执行InitHandler,然后开始组装当前channel的pipline,
跟着pipeline().addLast()一路走下去,发现了一个颠覆了我以前对netty的一些错误看法,之前我一直认为,addLast方法会检测当前加入的handler是不是可共享的,如果是可共享的,直接从缓冲中吧之前的同类型的handler从缓冲拿出来用就是了,结果万万没想到,原来netty并没有那么智能。
netty仅仅是判断了以下而已,如下checkMultiplicity(handler);,防止没有@sharable注解的实例被当成单例使用。。。。。。并没有那么智能。。。也就是说handler的单例需要自己实现。
netty addLast最终执行的代码
@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()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
checkMultiplicity(handler);
很明显,判断handler是不是共享的,然后是不是首次添加,不满足其一,直接抛异常。
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;
}
}
改进之后的InitHandler
作为成员变量,initHandler实例化之后,statusHandler就是唯一的了。
public class InitHandler extends ChannelInitializer<NioSocketChannel> {
private StatusHandler statusHandler = new StatusHandler();
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
System.out.println(this);
nioSocketChannel.pipeline()
.addLast(new TimeoutHandler(5,1000,1000))
.addLast(new MessageHandler())
.addLast(statusHandler)
.addLast(new WriteHandler<String>());
}
}
总结
- 博客吸纳性参考
- 多看源码了解原理
- 针对上述(代码2),添加@ChannelHandler.Sharable没有实质性的用途。
- ChannelInitializer全程只有一个,可以在这里做一些统计的事情
- 。。。。
为什么要把handler作为单例使用?
- 1.方便统计一些信息,如连接数
- 2.方便再所有channel值间共享以下而信息
- 3.但是要注意线程同步的问题
- 。。。。