家族战队|Java 序列化界新贵 kryo 和熟悉的“老大哥”( 二 )


首先 , 创建 Kryo 对象池 , 通过重写 Pool 接口的 create 方法 , 便可创建出自定义配置的对象池 。
private static final Pool kryoPool = new Pool(true, false, 512) {@Overrideprotected Kryo create() {Kryo kryo = new Kryo();// 关闭序列化注册 , 会导致性能些许下降 , 但在分布式环境中 , 注册类生成ID不一致会导致错误kryo.setRegistrationRequired(false);// 支持循环引用 , 也会导致性能些许下降 T_Tkryo.setReferences(true);return kryo;}};当需要使用 kryo 时 , 调用 kryoPool.obtain() 方法即可 , 使用完毕后再调用 kryoPool.free(kryo) 归还对象 , 就完成了一次完整的租赁使用 。
public static byte[] serialize(Object obj) {Kryo kryo = kryoPool.obtain();// 使用 Output 对象池会导致序列化重复的错误(getBuffer返回了Output对象的buffer引用)try (Output opt = new Output(1024, -1)) {kryo.writeClassAndObject(opt, obj);opt.flush();return opt.getBuffer();}finally {kryoPool.free(kryo);}}对象池技术是所有并发安全方案中性能最好的 , 只要对象池大小评估得当 , 就能在占用极小内存空间的情况下完美解决并发安全问题 。 这也是 PowerJob 诞生初期使用的方案 , 直到...PowerJob 正式推出容器功能后 , 才不得不放弃该完美方案 。
在容器模式下 , 使用 kryo 对象池计算会有什么问题呢?这里简单给大家提一下 , 至于看不看得懂 , 就要看各位造化了~
PowerJob 容器功能指的是动态加载外部代码进行执行 , 为了进行隔离 , PowerJob 会使用单独的类加载器完成容器中类的加载 。 因此 , 每一个 powerjob-worker 中存在着多个类加载器 , 分别是系统类加载器(负责项目的加载)和每个容器自己的类加载器(加载容器类) 。 序列化工具类自然是 powerjob-worker 的一部分 , 随 powerjob-worker 的启动而被创建 。 当 kryo 对象池被创建时 , 其使用的类加载器是系统类加载器 。 因此 , 当需要序列化/反序列化容器中的类时 , kryo 并不能从自己的类加载器中获取相关的类信息 , 妥妥的抛出 ClassNotFoundError!
因此 , PowerJob 在引入容器技术后 , 只能退而求其次 , 采取了第二种并发安全方法:ThreadLocal 。
1.4 ThreadLocalThreadLocal 是一种典型的牺牲空间来换取并发安全的方式 , 它会为每个线程都单独创建本线程专用的 kryo 对象 。 对于每条线程的每个 kryo 对象来说 , 都是顺序执行的 , 因此天然避免了并发安全问题 。 创建方法如下:
private static final ThreadLocal kryoLocal = ThreadLocal.withInitial(() -> {Kryo kryo = new Kryo();// 支持对象循环引用(否则会栈溢出) , 会导致性能些许下降 T_Tkryo.setReferences(true); //默认值就是 true , 添加此行的目的是为了提醒维护者 , 不要改变这个配置// 关闭序列化注册 , 会导致性能些许下降 , 但在分布式环境中 , 注册类生成ID不一致会导致错误kryo.setRegistrationRequired(false);// 设置类加载器为线程上下文类加载器(如果Processor来源于容器 , 必须使用容器的类加载器 , 否则妥妥的CNF)kryo.setClassLoader(Thread.currentThread().getContextClassLoader());return kryo;});之后 , 仅需要通过 *kryoLocal*.get() 方法从线程上下文中取出对象即可使用 , 也算是一种简单好用的方案 。 (虽然理论性能比对象池差不少)
二、老牌框架:Jackson大名鼎鼎的 Jackson 相信大家都听说过 , 也是很多项目的御用 JSON 序列化/反序列化框架 。 在 PowerJob 中 , 本着不重复造轮子的原则 , 在 akka 通讯层 , 使用了 jackson-cbor 作为默认的序列化框架 。


推荐阅读