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 {
  
    @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());
    }
}

原因分析

可以确定的是,一个连接新建立的时候首先执行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 {
    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());
    }
}

总结

  1. 博客吸纳性参考
  2. 多看源码了解原理
  3. 针对上述(代码2),添加@ChannelHandler.Sharable没有实质性的用途。
  4. ChannelInitializer全程只有一个,可以在这里做一些统计的事情
  5. 。。。。

为什么要把handler作为单例使用?

  • 1.方便统计一些信息,如连接数
  • 2.方便再所有channel值间共享以下而信息
  • 3.但是要注意线程同步的问题
  • 。。。。

 

你可能感兴趣的:(服务器,JavaDebug记录,netty)