序列化难题何解?Java 中的支持 JSON、Hessian、Kryo、Protobuf 序列化器实现和应用

350 阅读16分钟

参考文献


Java 序列化

什么是 Java 序列化

  • 序列化:将Java对象转换为字节序列的过程
  • 反序列化: 把字节序列恢复成 Java 对象的过程

序列化的必要性

JVM 停止后想保存对象到磁盘或传输到远程机器。但硬件只认识二进制,所以要把对象转化为字节数组,这就是序列化。

序列化的用途

  • 序列化机制可以让对象保存到磁盘上,减轻内存压力的同时,起到了持久化的作用
  • 序列化机制让 Java 对象可以在网络传输

序列化器的实现方式

序列化器的实现方式

主流的比如: 基于 JDK, JSON, Hessian, Kryo, protobuf

序列化器优点缺点
原生 JDK Serializable1. 简单易用,对简单 Java 应用内对象持久化需求便捷。
2. 兼容性好,与 Java 语言及相关框架、组件集成度高,在标准 Java 生态系统内进行对象传递、存储等操作无大兼容性问题。
1. 性能相对于其他序列化框架低。
2. 序列化后生成数据体积大。
3. 不支持多语言项目。
JSON1. 易读性好,可读性强,便于人类理解和调试。
2. 跨语言支持广泛,几乎所有编程语言都有 JSON 的解析和生成库。
1. 序列化后的数据量相对较大,因为 JSON 使用文本格式存储数据,需要额外的字符表示键、值和数据结构。
2. 不能很好地处理复杂的数据结构和循环引用,可能导致性能下降或者序列化失败。
Hessian1. 二进制序列化,序列化后的数据量较小,网络传输效率高。
2. 支持跨语言,适用于分布式系统中的服务调用。
1. 性能较 JSON 略低,因为需要将对象转换为二进制格式。
2. 对象必须实现 Serializable 接口,限制了可序列化的对象范围。
Kryo1. 高性能,序列化和反序列化速度快。
2. 支持循环引用和自定义序列化器,适用于复杂的对象结构。
3. 无需实现 Serializable 接口,可以序列化任意对象。
1. 不跨语言,只适用于 Java。
2. 对象的序列化格式不够友好,不易读懂和调试。
Protobuf1. 高效的二进制序列化,序列化后的数据量极小。
2. 跨语言支持,并且提供了多种语言的实现库。
3. 支持版本化和向前/向后兼容性。
1. 配置相对复杂,需要先定义数据结构的消息格式。
2. 对象的序列化格式不易读懂,不便于调试。

基于原生 Serializable 实现

涉及 API

java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.Serializable
java.io.Externalizable

Serializable 接口

Serializable 接口是标记接口,无方法或字段。实现它标志类的对象可序列化。

public interface Serializable {
}

Externalizable 接口

Externalizable 继承 Serializable 接口,定义了 writeExternal()readExternal() 抽象方法。开发人员用 Externalizable 实现序列化和反序列化需重写这两个方法。

public interface Externalizable extends java.io.Serializable {

    void writeExternal(ObjectOutput out) throws IOException;


    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

ObjectOutputStream / ObjectInputStream

对象输出流和对象输入流

  • 对象输出流的 writeObject(Object obj) 方法可序列化指定对象,将字节序列写入目标输出流。
  • 对象输入流的 readObject() 方法从输入流读取字节序列,反序列化后返回对象。

使用

定义一个实体类实现 Serializable

import lombok.Data;
import java.io.Serializable;

@Data
public class User implements Serializable {

    private String userId;
    private Integer age;
    private String name;
}

定义 Serializer 接口

public interface Serializer {

    /**
     * 序列化
     *
     * @param object
     * @return
     * @throws IOException
     */
    void serialize(Object object) throws IOException;

    /**
     * 反序列化
     *
     * @param path
     * @param type
     * @param <T>
     * @return
     * @throws IOException
     */
    <T> T deserialize(String path, Class<T> type) throws IOException;
}

实现基于 JDK 的序列化器

  • 使用 ObjectOutputStream 类的 writeObject 方法,实现序列化

  • 使用 ObjectInputStream 类的 readObject 方法,实现反序列化

public class JdkSerializer implements Serializer {
    @Override
    public void serialize(Object object) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("src/main/resources/test.out")));

        objectOutputStream.writeObject(object);
        objectOutputStream.flush();
        objectOutputStream.close();
    }

    @Override
    @SuppressWarnings("all")
    public <T> T deserialize(String path, Class<T> type) throws IOException {
        ObjectInputStream objInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(path)));
        try {
            return (T) objInputStream.readObject();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

测试

public class SerializeDirector {

    public static void main(String[] args) {

        Serializer serializer = new JdkSerializer();

        User user = new User();
        user.setName("Elon Mask");
        user.setAge(45);
        user.setUserId("11111");

        try {
            serializer.serialize(user);

            User deserializeUser = serializer.deserialize("src/main/resources/test.out", User.class);

            System.out.println(deserializeUser);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

输出

注意

1. static 静态变量和 transient 修饰的字段是不会被序列化的

静态成员变量属于类级别,因序列化针对对象,故不能序列化。 transient 关键字,它可以阻止修饰的字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如 int 型的值会被设置为 0

2. serilalVersionUID 的作用

JAVA 序列化通过判断类的 serialVersionUID 验证版本一致。反序列化时,JVM 比较传来字节流与本地实体类的 serialVersionUID,相同则反序列化成功,不同则抛出 InvalidClassException 异常。

《阿里巴巴Java开发手册》中关于serialVersionUID的使用规约

  • 强制:序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值.

这样规定的原因

  • 保证序列化与反序列化的兼容性
  • 避免反序列化失败导致的系统错误
  • 便于开发者控制版本和兼容性

3. 如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化

4. 子类实现了 Serializable, 父类没有实现 Serializable 接口的话,父类不会被序列化


实现 JSON 序列化

主流的 JSON 工具有:

  • Jackson
  • FastJson
  • Gson
  • Hutool

关于以上各个工具的性能比较可以参考: 测试 JSON 工具的版本

基于 Jackson 实现

导入依赖

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.13.4</version>
</dependency>

扩展实体类

@Data
public class User implements Serializable {

    private String userId;
    private Integer age;
    private String name;

    List<String> hobbies;
    Map<String, Integer> parents;   //家人 key 年龄 val

    public static User getInstance() {

        User user = new User();
        user.setUserId("111");
        user.setAge(22);
        user.setName("Elon Mask");

        user.hobbies = new ArrayList<String>() {{
            add("AAA");
            add("BBB");
            add("CCC");
        }};

        user.parents = new HashMap<String, Integer>() {
            {
                put("ABC", 111);
                put("SHUC", 123);
            }
        };

        return user;
    }
}

实现基于 Jackson 序列化器

  • writeValue可以接收File作为参数,将 JSON 序列化结果保存到文件中
  • writeValueAsString 将JSON序列化结果以String形式返回
  • writerWithDefaultPretty Printer方法可以将JSON序列化结果进行格式化,更好的显示结构,易于查看
public class JacksonSerializer implements Serializer {


    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void serialize(Object object) throws IOException {

        //Object Mapper 作为 Jackson 的 API 工具类存在
        objectMapper.writeValue(new File("src/main/resources/testJson.out"), object);

        //将 user 对象以 JSON 格式进行序列化 String 对象
        String jsonStr = objectMapper.writeValueAsString(object);
        System.out.println(jsonStr);

        //格式美化
        String prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
        System.out.println(prettyJson);
    }

    //从文件中读取JSON字符串,反序列化为java对象
    @Override
    public <T> T deserialize(String path, Class<T> type) throws IOException {
        return (T) objectMapper.readValue(new File(path), type);
    }

    //将JSON字符串反序列化为java对象
    public User parseUser(String str) throws JsonProcessingException {
        String jsonInString = "{"name":"乔丹","age":45,"hobbies":["高尔夫球","棒球"]}";
        return objectMapper.readValue(jsonInString, User.class);
    }
}

测试

    @Test
    public void testJsonSerializer() throws IOException {

        String targetPath = "src/main/resources/testJson.out";
        Serializer serializer = new JacksonSerializer();

        serializer.serialize(User.getInstance());

        User deserializedUser = serializer.deserialize(targetPath, User.class);
        System.out.println(deserializedUser);

        User parseUser = ((JacksonSerializer) serializer).parseUser("{"name":"乔丹","age":45,"hobbies":["高尔夫球","棒球"]}");
        System.out.println(parseUser);
    }

序列化结果

反序列化结果

Jackson 常用注解

注解 - @JsonProperty

@JsonProperty 可影响对象属性在序列化和反序列化时的重命名。

@Data
public class User implements Serializable {

    ....
    @JsonProperty("User's parents")
    Map<String, Integer> parents;   //家人 key 年龄 val

}
注解 - @JsonInclude

不想让 null 值出现在 JSON 序列化结果中,可使用下面方法。

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private Integer age;

忽略字段

    public static User getInstance() {

        User user = new User();
        user.setUserId("111");
//        user.setAge(22);
        user.setName("Elon Mask");

        
    }

若要在某次序列化全局范围忽略 null 成员变量,可用下面 API。

借助 API - setSerializationInclusion(JsonInclude.Include.NON_NULL)

    ....
    
    private ObjectMapper objectMapper = new ObjectMapper();

    {
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
    
    ....

测试 - 序列化后移除为 null 的成员变量

注解 - 忽略指定字段
  • @JsonIgnore —— 加在类成员变量上面,该成员变量将被排除在序列化和反序列化的过程之外
  • @JsonIgnoreProperties—— 加在类声明上面,指定该类里面哪些字段被排除在序列化和反序列化的过程之外

基于 FastJson2 实现

导入依赖

<!--        测试-多种序列化方法-->
<dependency>
  <groupId>com.alibaba.fastjson2</groupId>
  <artifactId>fastjson2</artifactId>
  <version>2.0.8</version>
</dependency>

常用类和 API

package com.alibaba.fastjson2;


class JSON {
    // 将字符串解析成JSONObject
    static JSONObject parseObject(String str);

    // 将字符串解析成JSONArray
    static JSONArray parseArray(String str);

    // 将字符串解析成Java对象
    static T parseObject(byte[] utf8Bytes, Class<T> objectClass);

    // 将Java对象输出成字符串
    static String toJSONString(Object object);

    // 将Java对象输出成UT8编码的byte[]
    static byte[] toJSONBytes(Object object);

}


class JSONObject {
    Object get(String key);
    int getIntValue(String key);
    Integer getInteger(String key);
    long getLongValue(String key);
    Long getLong(String key);
    T getObject(String key, Class<T> objectClass);

    // 将JSONObject对象转换为Java对象
    T toJavaObject(Class<T> objectClass);

}

class JSONReader {
    // 构造基于String输入的JSONReader
    static JSONReader of(String str);

    // 构造基于ut8编码byte数组输入的JSONReader
    static JSONReader of(byte[] utf8Bytes);

    // 构造基于char[]输入的JSONReader
    static JSONReader of(char[] chars);

    // 构造基于json格式byte数组输入的JSONReader
    static JSONReader ofJSONB(byte[] jsonbBytes)

}

测试 - 将字符串转换为对象

@Test
public void testParseJsonByFastJson() {
    //转换 JSONObject
    String str = "{"name":"乔丹","age":45,"hobbies":["高尔夫球","棒球"]}";

    JSONObject jsonObject = JSON.parseObject(str);
    System.out.println(jsonObject.toJSONString());

    System.out.println("Name = " + jsonObject.getString("name"));
    System.out.println("Age = " + jsonObject.getIntValue("age"));
}

输出结果

测试 - 将对象转换为 JSON 字符串

@Test
public void testObj2Json() {
    User user = User.getInstance();
    String jsonStr = JSON.toJSONString(user);
    System.out.println(jsonStr);
}

输出结果

基于 FastJson2 实现序列化器

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FastJson2Serializer implements Serializer {

    @Override
    public void serialize(Object object) throws IOException {
        String jsonString = JSON.toJSONString(object);
        String filePath = "src/main/resources/test_fastjson2.out";

        // Write the JSON string to a file
        Files.write(Paths.get(filePath), jsonString.getBytes());
    }

    @Override
    public <T> T deserialize(String path, Class<T> type) throws IOException {
        // Read the JSON string from the file
        String jsonString = new String(Files.readAllBytes(Paths.get(path)));

        // Convert JSON string back to an object of the given type
        return JSON.parseObject(jsonString, type);
    }
}

测试

    @Test
    public void testFastJson2Converter() {

        FastJson2Serializer fastJson2Serializer = new FastJson2Serializer();
        User result = null;
        try {
            fastJson2Serializer.serialize(User.getInstance());
            result = fastJson2Serializer.deserialize("src/main/resources/test_fastjson2.out", User.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        System.out.println(result);
    }


基于 Hessian 序列化

介绍

Hessian 是 Caucho Technology 开发的轻量级二进制序列化框架,可用于不同编程语言间高效对象序列化和反序列化,实现跨语言 RPC 或对象传输。与 JSON、XML 等文本格式序列化方式相比,Hessian 序列化后的二进制数据体积小、传输效率高,在分布式系统和网络应用中广泛应用。

基于二进制实现 Hessian 也是 Dubbo 中默认的序列化机制

导入依赖

<dependency>
  <groupId>com.caucho</groupId>
  <artifactId>hessian</artifactId>
  <version>4.0.66</version>
</dependency>

测试常用方法

    @Test
    public void testHessianConvert() throws Exception {

        //序列化
        FileOutputStream outputStream = new FileOutputStream("src/main/resources/testHessian.out");
        Hessian2Output hop = new Hessian2Output(outputStream);
        hop.writeObject(User.getInstance());
        hop.flush();
        hop.close();

        //反序列化
        FileInputStream inputStream = new FileInputStream("src/main/resources/testHessian.out");
        Hessian2Input hip = new Hessian2Input(inputStream);
        User user = (User) hip.readObject();
        hip.close();
        System.out.println(user);
    }


基于 Protobuf 序列化

介绍

Protobuf 是 Google 开发的数据序列化和反序列化工具,类似 XML 和 JSON,更小巧、快速、简单,用于不同应用程序间数据交换和存储,在网络通信和分布式系统中表现出色。

Protobuf 优势

  • 高效: 二进制格式序列化,数据量小,省带宽和存储资源,提传输存储效率;
  • 跨语言: 支持多编程语言,方便异构系统集成通信;强类型且兼容,.proto 文件定义数据结构,减少错误风险,支持字段操作兼容,便于系统升级;
  • 代码生成自动化,根据.proto 文件自动生成代码,减工作量提效率,性能高。

Protobuf VS Json

  • Json 是字符串数据,转换需先转流再转字符串。与二进制序列化技术比,效率和性能有差异。
  • Json 字符串以明文传输有数据泄露风险。
  • Json 生成字符串数据体积较大,相比二进制序列化技术,传输有额外损耗。

美中不足: 使用 ProtoBuf 技术不像 JDK、Json 序列化便捷,很麻烦。生成的 Java 代码与传统风格不同,对原有代码侵入性高,改造成本大。

应用场景

  • 分布式系统通信: 分布式系统中,Protobuf 因高效和跨语言性,是不同服务数据交互的理想选择,能快速传输数据、降低通信延迟、提升系统性能。
  • 数据存储: 它可将需长期存储的数据序列化为紧凑二进制格式,节省空间且提高访问效率。
  • 远程过程调用: 在 RPC 框架中,可作为数据序列化标准传递参数和返回值
  • 配置文件管理: 还能用于定义和解析配置文件,方便共享和更新。

Protobuf 环境安装

参考文档

安装 ProtoBuf 编译器 - [Win 为例]

  1. 进入 protobuf的 github的发布地址: - protobuf 编译器发布地址

  2. 选取标准版比如 - v.3.x.x 我选择的是 v3.2.0

  3. 下载压缩包比如 Win 下载 XXX-win64.zip

  4. 安装完成后,将 ⌈protoc 安装目录⌋ /bin 路径添加到 PATH 环境变量

  5. 打开 cmd,命令窗口执行 protoc命令

确认安装版本 - protoc --version

测试 - 常用方法

导入依赖

<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version>3.21.12</version>
</dependency>

项目当前包下创建一份 .proto文件

syntax = "proto3";

// 当前 .proto 文件所在的目录
package com.jools.javase.serialize;

// .proto 文件生成 .java 文件时的目录
option java_package = "com.jools.javase.serialize.protoc";

//生成的 Java 文件名
option java_outer_classname = "ProtoUser";

//生成 equals 和 hash 方法
option java_generate_equals_and_hash = true;

//是否划分为多个文件
option java_multiple_files = false;

message User {
  string name = 1;
  int32 age = 2;
  string email = 3;
}

安装插件 (可选)

借助 IDEA 生成

右键 .protoc 文件 - 生成可选项

quick gen protobuf here:在.proto文件所在的目录下生成.java文件;

quick gen protobuf rules:根据.proto中配置的java_package生成.java文件

点击 quick gen protobuf rules

生成了基于配置的 ProtoUser 类名的 .java 文件

基于 Protobuf 实现序列化/反序列化

将对象序列化为字节数组 / 反序列化

@Test
public void testProtoBufEasy() throws InvalidProtocolBufferException {

    //通过 Builder 创建实例化对象
    ProtoUser.User.Builder newUser = ProtoUser.User.newBuilder();
    
    //实例化对象填充数据
    newUser.setName("Elon Mask").setAge(45).setEmail("SpaceX001@gmail.com");
    
    //构建实例化对象
    ProtoUser.User build = newUser.build();
    byte[] userBytes = build.toByteArray();
    System.out.println("Protobuf压缩之后得到的长度:" + userBytes.length);
    
    //反序列化
    ProtoUser.User user = ProtoUser.User.parseFrom(userBytes);
    System.out.println(user);
}

输出

序列化成字节数组 + 反序列化字节数组

import com.google.protobuf.Message;
import com.google.protobuf.Parser;
import com.jools.rpc.serializer.Serializer;

import java.io.IOException;

public class ProtoBufSerializer implements Serializer {

    /**
     * 序列化后,将对象转换为字节数组
     *
     * @param object 需要被序列化的对象
     * @param <T>    对象类型
     * @return 序列化后的字节数组
     * @throws IOException 如果序列化失败
     */
    @Override
    public <T> byte[] serialize(T object) throws IOException {
        if (object instanceof Message) {
            return ((Message) object).toByteArray();
        } else {
            throw new IllegalArgumentException("Object must be an instance of com.google.protobuf.Message");
        }
    }

    /**
     * 反序列化,将字节数组转成对象实例
     *
     * @param bytes 字节数组
     * @param type  目标类的 Class 类型
     * @param <T>   目标类的类型
     * @return 反序列化后的对象
     * @throws IOException
     */
    @Override
    public <T> T deserialize(byte[] bytes, Class<T> type) throws IOException {
        try {
            // 检查类型是否是 Protobuf Message 的子类
            if (Message.class.isAssignableFrom(type)) {
                // 获取 Protobuf 的 parser 方法
                // Protobuf 生成的类通常会有静态的 `parser()` 方法
                Parser<?> parser = (Parser<?>) type.getMethod("parser").invoke(null);
                // 解析字节数组
                @SuppressWarnings("unchecked")
                T message = (T) parser.parseFrom(bytes);
                return message;
            } else {
                throw new IllegalArgumentException("Type must be a subclass of com.google.protobuf.Message");
            }
        } catch (Exception e) {
            throw new IOException("Failed to deserialize", e);
        }
    }
}

ProtoBuf 内的常用数据类型

Protobuf类型对应Java类型默认值说明
int32int整数型0常规的有符号32位整数类型
int64long长整型0常规的有符号64位整数类型
floatfloat浮点型0单精度浮点型
doubledouble双精度浮点型0双精度浮点型
boolboolean布尔型false表示布尔值
stringString字符串空字符串用于存储字符串数据
bytesByteString类型无(表示字节序列)对应字节序列数据
sint32int整数型0有符号的整数类型,编码负数时比int32高效
unit32int整数型0无符号的整数类型,编码正数时比int32高效
sint64long长整型0有符号的64位长整型
unit64long长整型0无符号的64位长整型

支持 枚举、内部类 语法

syntax = "proto3";

message User {

  //字段值不可重复
  uint32 id = 1;

  //Field number 1 has already been used by field 'id'
  
//  string name = 1;
  string name = 2;

  //使用 Hobby 统计枚举作为字段
  Hobby hobby = 3;

  //使用内部 Pet
  Pet pet = 4;

  message Pet {
    string name = 5;
    uint32 age = 6;
  }
}

enum Hobby {
  //First enum value must be 0 in proto3
  FOOT_BAELL = 0;
}

支持 List \ Map 数据类型

  //List
  repeated Hobby hobbies = 7;
  //Map
  map<string, Pet> pets = 8;

基于 Kryo 实现序列化

介绍

Kryo 是快速高效的 Java 对象序列化框架,专注高性能序列化和反序列化,比 Java 标准序列化及其他常见方式在速度和数据大小方面有优势,在高性能要求的 Java 应用中广泛使用。

Kryo 优势

  • 高性能
  • 序列化后数据量小
  • 支持复杂对象结构

基于 Kryo 序列化/反序列化

Kryo 为了提供性能和减少序列化结果的体积,提供注册方式

    @Test
    public void testKryoEasy() {

        Kryo kryo = new Kryo();

        User user = new User();
        user.setName("Elon Mask");
        user.setAge(45);
        user.setUserId("0001");

        //需要注册
        kryo.register(User.class);

        //可以指定 ID, 必须大于 0
        // kryo.register(User.class, 1);

        //序列化对象 -> 字节数组
        ByteArrayOutputStream bOpt = new ByteArrayOutputStream();
        Output output = new Output(bOpt);
        kryo.writeObject(output, user);
        output.close();
        byte[] byteArray = bOpt.toByteArray();
        System.out.println("Kryo 压缩之后得到的字节数组长度:" + byteArray.length);

        //反序列化为对象
        ByteArrayInputStream bInpt = new ByteArrayInputStream(byteArray);
        Input input = new Input(bInpt);
        User deserialized = kryo.readObject(input, User.class);
        input.close();

        System.out.println("反序列化后得到:" + deserialized);
    }

输出

问题 - Kryo 线程不安全

  • 内部状态修改: Kryo 在序列化和反序列化中维护内部状态,如注册类信息、对象引用缓存等。多线程同时访问和修改易致数据不一致或错误结果,如线程注册新类时另一线程序列化可能找不到正确类信息引发异常。
  • 缓存机制: Kryo 用缓存提高性能,但多线程下可能同时访问修改缓存致数据不一致,如一个线程放对象入缓存另一个同时查找修改会混乱。
  • 缺乏同步机制: Kryo 设计初衷为高性能,缺乏完善线程同步机制,多线程并发易有线程安全问题。

解决 - Kryo 线程不安全序列化/反序列化

方式一:基于 ThreadLocal

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;


public class KryoSerializer implements Serializer {

    /**
     * Kryo 实例应该是线程不安全的,所以每个线程应该有自己的 Kryo 实例
     */
    private static final ThreadLocal<Kryo> KRYO_THREAD_LOCAL = ThreadLocal.withInitial(Kryo::new);

    @Override
    public <T> byte[] serialize(T object) throws IOException {
        Kryo kryo = KRYO_THREAD_LOCAL.get();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Output output = new Output(byteArrayOutputStream);
        kryo.writeObject(output, object);
        output.close();
        return byteArrayOutputStream.toByteArray();
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> type) throws IOException {
        Kryo kryo = KRYO_THREAD_LOCAL.get();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Input input = new Input(byteArrayInputStream);
        T object = kryo.readObject(input, type);
        input.close();
        return object;
    }
}

方式二: 基于池技术

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.util.Pool;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;



public class KryoSerializer implements Serializer {
    
    private static Pool<Kryo> kryoPool = new Pool<>(true, false, 8) {
        @Override
        protected Kryo create() {
            return new Kryo();
        }
    };

    @Override
    public <T> byte[] serialize(T object) throws IOException {
        //从池中获取对象
        Kryo kryo = kryoPool.obtain();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Output output = new Output(byteArrayOutputStream);
        kryo.writeObject(output, object);
        output.close();
        //将 Kryo 对象归还
        kryoPool.free(kryo);
        return byteArrayOutputStream.toByteArray();
    }

    @Override
    public <T> T deserialize(byte[] bytes, Class<T> type) throws IOException {
        //从池中获取对象
        Kryo kryo = kryoPool.obtain();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Input input = new Input(byteArrayInputStream);
        T object = kryo.readObject(input, type);
        input.close();
        //将 Kryo 对象归还
        kryoPool.free(kryo);
        return object;
    }
}

详见:对象池 + Kryo 解决线程不安全部分