一、@Sharable的真实含义
- 当netty尝试往多个channel的pipeline中
添加同一个ChannelHandlerAdapter实例时,会判断该实例类是否添加了@Sharable
,没有则抛出… is not a @Sharable handler, so can’t be added or removed multiple times异常
如果你添加的
不是单例Handler,你加不加@Sharable没有任何区别
如果
你添加的是单例Handler,只要它会被添加到多个channel的pipeline,那就必须加上@Sharable
- 网上有些资料说netty会将注解了@Sharable的Handler单例化,实在误导人。channelHandler是不是单例跟netty没有任何关系,netty只会在你尝试用单例ChannelHandler时加上第1条说明的限制,这个限制意思很明确,多个channel公用单例ChannelHandler,那它就必须是线程安全的,
@Sharable就是用于告诉netty,我这个Handler是线程安全的,可以被多个channel安全的share,假如你搞了个线程不安全的类,你对它用上了单例,还加个@Sharable,这时候netty拿你也没办法,只是你的老板可能会有点不开心
- 一般情况下,我们的代码是这样:
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
//每次都是new一个新对象,这时候你 ShareTestChannelHandler这个类加不加@Sharable没有任何区别
ch.pipeline().addLast(new ShareTestChannelHandler());
};
});
在initChannel方法中,我们addLast传入的Handler实例,他是不是单例的与netty没有任何关系,和你是否注解@Sharable也没有任何关系。就拿上面的代码说,它就不是单例的,每次都是new一个新对象,这时候你ShareTestChannelHandler这个类加不加@Sharable没有任何区别。
- 如下代码
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
//每次传入的都是单例的Handler对象,只要它会被添加到多个channel的pipeline链中,那它就必须是@Sharable的,不然就抛出第1点说的异常
ch.pipeline().addLast(new ShareTestChannelHandler());
};
});
@Sharable
public class ShareTestChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
public static ShareTestChannelHandler INSTANCE = new ShareTestChannelHandler();
private ShareTestChannelHandler() {}
......
}
如果,你传入一个单例的Handler对象,只要它会被添加到多个channel的pipeline链中,那它就必须是@Sharable的,不然就抛出第1点说的异常
二、是不是所有Handler都可以加上@Sharable
- 不是,netty中,
解码器有关的Handler都是不安全的,因为粘包拆包的缘故,Decoder必须要保存一些解析过程的中间状态
,比如ByteToMessageDecoder类中维护的一个字节累加器cumulation,每次读到当前channel的消息后都会将消息累加到cumulation中,然后再调用子类实现的decode方法。所以它不能被多个channel安全的共享,netty明确的禁止了使用单例Decoder,即使你自作聪明的给他加上@Sharable,也会被禁止掉,抛出... is not allowed to be shared错误
。 - 所以我们在添加Decoder时:
- 必须不是单例
- 不要添加@Sharable
- 例子
@Slf4j
//这里添加了@Sharable,肯定会报错
@ChannelHandler.Sharable
public class MessageCodec extends ByteToMessageCodec<Message> {
@Override
public void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
// 1. 4 字节的魔数
out.writeBytes(new byte[]{1, 2, 3, 4});
// 2. 1 字节的版本,
out.writeByte(1);
// 3. 1 字节的序列化方式 jdk 0 , json 1
out.writeByte(0);
// 4. 1 字节的指令类型
out.writeByte(msg.getMessageType());
// 5. 4 个字节
out.writeInt(msg.getSequenceId());
// 无意义,对齐填充,让固定字节数为2的n次方倍
out.writeByte(0xff);
// 6. 获取内容的字节数组(序列化)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(msg);
byte[] bytes = bos.toByteArray();
// 7. 长度
out.writeInt(bytes.length);
// 8. 写入内容
out.writeBytes(bytes);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int magicNum = in.readInt();
byte version = in.readByte();
byte serializerType = in.readByte();
byte messageType = in.readByte();
int sequenceId = in.readInt();
in.readByte();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
Message message = (Message) ois.readObject();
log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
log.debug("{}", message);
//可能解析出多条信息,加到list里面,给下一个handler处理
out.add(message);
}
}
但是,如果我们明确知道MessageCodec是线程安全的),要使用@Sharable,就不要继承ByteToMessageCodec了,可以通过继承 MessageToMessageCodec来实现。
@Slf4j
@ChannelHandler.Sharable
/**
* 必须和 LengthFieldBasedFrameDecoder 一起使用,以确保接到的 ByteBuf 消息是完整的,防止没接收到完整信息,又切换到其他channel使用,导致线程不安全
*/
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {
@Override
public void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
ByteBuf out = ctx.alloc().buffer();
// 1. 4 字节的魔数
out.writeBytes(new byte[]{1, 2, 3, 4});
// 2. 1 字节的版本,
out.writeByte(1);
// 3. 1 字节的序列化方式 jdk 0 , json 1
out.writeByte(Config.getSerializerAlgorithm().ordinal());
// 4. 1 字节的指令类型
out.writeByte(msg.getMessageType());
// 5. 4 个字节
out.writeInt(msg.getSequenceId());
// 无意义,对齐填充
out.writeByte(0xff);
// 6. 获取内容的字节数组
byte[] bytes = Config.getSerializerAlgorithm().serialize(msg);
// 7. 长度
out.writeInt(bytes.length);
// 8. 写入内容
out.writeBytes(bytes);
outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int magicNum = in.readInt();
byte version = in.readByte();
byte serializerAlgorithm = in.readByte(); // 0 或 1
byte messageType = in.readByte(); // 0,1,2...
int sequenceId = in.readInt();
in.readByte();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
// 找到反序列化算法
Serializer.Algorithm algorithm = Serializer.Algorithm.values()[serializerAlgorithm];
// 确定具体消息类型
Class<? extends Message> messageClass = Message.getMessageClass(messageType);
Message message = algorithm.deserialize(messageClass, bytes);
out.add(message);
}
}
三、总结
加了@Sharable的类,netty就认为是线程安全,可以共享,而只要netty明确的禁止了使用单例Handler,即使你给他加上@Sharable,也会被禁止掉。
同时,如果一个类是单例的Handler对象,同时他会被添加到多个channel的pipeline链中,那它就必须是@Sharable的,不然也会报错。
- handler 不保存状态时,就可以安全地在多线程下被共享
- 但要注意对于编解码器类,
不能继承 ByteToMessageCodec 或 CombinedChannelDuplexHandler 父类
,他们的构造方法对 @Sharable 有限制- 如果
能确保编解码器不会保存状态,可以继承 MessageToMessageCodec 父类