注解说明
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注解。
源码分析
@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 意味着可以共享,可以共享才可以添加。 否则就不让添加。
/** * 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; }
代码示例
/**
* 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
}
}
![](https://img2020.cnblogs.com/blog/493842/202106/493842-20210614131541271-211053588.png)
可见,其中 ConcurrentMap、AtomicLong、 volatile 保证了线程安全; 其中maxGlobalWriteSize 是volatile ,只需要保证 可见性即可。why ? 因为它的值不是累加的,后面的值不依赖于之前的旧值。
使用场景
使用的场景就是 适用单例的地方,
为什么要把handler作为单例使用? 1.方便统计一些信息,如连接数 2.方便再所有channel值间共享以下而信息 。。
明白了 单例, 也就明白了它。
通常的使用场景:
1 handler中的计数服务,需要一直在递增。
2 使用这种线程共享的handler可以避免频繁创建handler带来的系统开销
3 适用于某些支持线程共享的handler,比如日志服务,计数服务等。
4 适用于没有成员变量的encoder、decoder
5 粘包问题的拆包的时候,需要共享一下中间数据、变量
..