注解說明
@Sharable 的作用其實非常簡單,也不難理解,但是官方的說明有點難理解。
Indicates that the same instance of the annotated ChannelHandler can be added to one or more ChannelPipelines multiple times without a race condition.If this annotation is not specified, you have to create a new handler instance every time you add it to a pipeline because it has unshared state such as member variables.This annotation is provided for documentation purpose, just like the JCIP annotations.標識同一個ChannelHandler的實例可以被多次添加到多個ChannelPipelines中,而且不會出現競爭條件。如果一個ChannelHandler沒有標志@Shareable,在添加到到一個pipeline中時,你需要每次都創建一個新的handler實例,因為它的成員變量是不可分享的。這個注解僅作為文檔參考使用,比如說JCIP注解。
有了@Sharable 就一定保證了不會出現競爭條件? 測試證明這里 不太准確。官方的模糊說明,最為致命。WTF
經過很多很多的測試,發現它只對自定義的 Handler在添加到pipeline的時候 有一點作用。其實很簡單,兩個情況:
1 如果每次通過new 而不是共享的方式,那么加不加@Sharable 效果都是一樣的。每個Channel使用不通的ChannelHandler 對象。
如在 .childHandler(new ChannelInitializer<SocketChannel>() { 中這樣寫:
pipeline().addLast(new EchoServerHandler());
這個方式是 每次都創建一個新的實例,其實就不會檢查是否Sharable ,因為肯定是 unSharable
2 如果通過共享的方式,也就是 Handler 實例只有一個,那么必須要加@Sharable ,表明它是可以共享的,否則 第二次建立連接的時候會報錯:
io.netty.channel.ChannelPipelineException: xxxHandler is not a @Sharable handler, so can't be added or removed multiple times.
這樣做的目的 大概是 以防 使用方 忘記了 實例是可以共享的, 需要他創建自定義Handler 的時候就引起注意。
源碼分析
首先 DefaultChannelPipeline所有的addXxx 方法, 有調用checkMultiplicity,從而保證了邏輯一致。如:
@Override public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) { final AbstractChannelHandlerContext newCtx; synchronized (this) { checkMultiplicity(handler); ...
然后,在DefaultChannelPipeline#checkMultiplicity:
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; } }
因為checkMultiplicity方法每次 添加連接都會執行,那么第一次執行完之后 h.added 就是 true, 后面如何在添加連接, 那么還會執行到這里,那么 如果是共享的 h , 也就是如果一個實例, 同一個h, 那么它的 added 字段值就還是 true,那么 就需要判斷 h.isSharable() ,h.isSharable() == true 意味着可以共享,可以共享才可以添加。 否則就不讓添加。
這是一個強制的做法。 就是強制如果需要共享, 就必須添加 @Sharable 注解。
這樣做的目的 大概是 以防 使用方 忘記了 實例是可以共享的, 需要他創建自定義Handler 的時候就引起注意。
不同Handler需要共享信息的時候, 干脆就使用一個Handler,而不是多個。
對於一般的TCP ,其實現在io.netty.channel.ChannelHandlerAdapter#isSharable 方法。
/** * Return {@code true} if the implementation is {@link Sharable} and so can be added * to different {@link ChannelPipeline}s. */ public boolean isSharable() { /** * Cache the result of {@link Sharable} annotation detection to workaround a condition. We use a * {@link ThreadLocal} and {@link WeakHashMap} to eliminate the volatile write/reads. Using different * {@link WeakHashMap} instances per {@link Thread} is good enough for us and the number of * {@link Thread}s are quite limited anyway. * * See <a href="https://github.com/netty/netty/issues/2289">#2289</a>. */ Class<?> clazz = getClass(); Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache(); Boolean sharable = cache.get(clazz); if (sharable == null) { sharable = clazz.isAnnotationPresent(Sharable.class); cache.put(clazz, sharable); } return sharable; }
總之,@Sharable注解定義在ChannelHandler接口里面,該注解被使用是在ChannelHandlerAdapter類里面,被sharable注解標記過的實例都會存入當前加載線程的threadlocal里面。
它和什么多線程、 線程安全, 有一定關系。因為 全局唯一實例意味着 多線程的競爭。
所以呢, 這種 存在 @Sharable 注解、自定義的、全局唯一的實例, 其內部屬性最好也要做成線程安全的,否則可能有線程問題。

代碼示例
/**
* netty 接收 次數 計數器
*/
@ChannelHandler.Sharable// 表示它可以被多個channel安全地共享
public class ShareableEchoServerHandler extends ChannelInboundHandlerAdapter {
private AtomicInteger integer = new AtomicInteger(0);
private long integeer = 0L;// 不能這樣寫。。
public ShareableEchoServerHandler() {
System.out.println(this.getClass()
.getSimpleName() + " init....");
}
// 從channel中讀取消息時
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println(integer.incrementAndGet() + " " + ctx.handler());
System.out.println(integeer ++ + " " + ctx.handler()); // 這樣做 看似也可以, 但是有多線程安全問題, 即潛在bug
// ctx.pipeline().fireChannelRead(msg ); // 第 388 次的時候: java.lang.StackOverflowErrorjava.lang.StackOverflowErrorjava.lang.StackOverflowError
ctx.channel().pipeline().fireChannelRead(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace();
ctx.close(); // 關閉該channel
}
}
看官方做法示例:

可見,其中 ConcurrentMap、AtomicLong、 volatile 保證了線程安全; 其中maxGlobalWriteSize 是volatile ,只需要保證 可見性即可。why ? 因為它的值不是累加的,后面的值不依賴於之前的舊值。
使用場景
使用的場景就是 適用單例的地方,
為什么要把handler作為單例使用? 1.方便統計一些信息,如連接數 2.方便再所有channel值間共享以下而信息 。。
明白了 單例, 也就明白了它。
通常的使用場景:
1 handler中的計數服務,需要一直在遞增。
2 使用這種線程共享的handler可以避免頻繁創建handler帶來的系統開銷
3 適用於某些支持線程共享的handler,比如日志服務,計數服務等。
4 適用於沒有成員變量的encoder、decoder
5 粘包問題的拆包的時候,需要共享一下中間數據、變量
..
總結
總結一下,它其實就是為了共享的方面,然后為了提升一點性能。
其用法很簡單,兩個情況:
1 如果每次通過new 而不是共享的方式,那么加不加@Sharable 效果都是一樣的。每個Channel使用不通的ChannelHandler 對象。
如:ch.pipeline().addLast(new EchoServerHandler());
2 如果通過共享的方式,也就是 Handler 實例只有一個,那么必須要加@Sharable ,表明它是可以共享的,否則 第二次建立連接的時候會報錯:
io.netty.channel.ChannelPipelineException: xxxHandler is not a @Sharable handler, so can't be added or removed multiple times.
另外,對於存在 @Sharable 注解、自定義的、全局唯一的實例,要注意線程同步的問題,其內部屬性最好也要做成線程安全的,否則可能有線程問題。
參考:
https://www.cnblogs.com/technologykai/articles/10907567.html
https://blog.csdn.net/weixin_35891744/article/details/114742844?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-4&spm=1001.2101.3001.4242