一直以來,我都以爲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.但是要注意線程同步的問題
- 。。。。