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

總結

  1. 博客吸納性參考
  2. 多看源碼瞭解原理
  3. 針對上述(代碼2),添加@ChannelHandler.Sharable沒有實質性的用途。
  4. ChannelInitializer全程只有一個,可以在這裏做一些統計的事情
  5. 。。。。

爲什麼要把handler作爲單例使用?

  • 1.方便統計一些信息,如連接數
  • 2.方便再所有channel值間共享以下而信息
  • 3.但是要注意線程同步的問題
  • 。。。。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章