Bug之java.io.OptionalDataException

产生背景:

在线上的生产环境中,登录时有一个将用户的访问权限写入redis缓存的逻辑,以便后面访问接口时候,快速验证用户是否具有权限。写入的时候,没有报错提示。但是取出来的时候,偶发性的会报Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.OptionalDataException。

分析:

写入是没有问题的,可以从redis里面看到确实写入的数据,但是数据是加密的,看不出来具体是什么。首先,还是看看java.io.OptionalDataException这个类的介绍。

/**
 * Exception indicating the failure of an object read operation due to
 * unread primitive data, or the end of data belonging to a serialized
 * object in the stream.  This exception may be thrown in two cases:
 *
 * 
    *
  • An attempt was made to read an object when the next element in the * stream is primitive data. In this case, the OptionalDataException's * length field is set to the number of bytes of primitive data * immediately readable from the stream, and the eof field is set to * false. * *
  • An attempt was made to read past the end of data consumable by a * class-defined readObject or readExternal method. In this case, the * OptionalDataException's eof field is set to true, and the length field * is set to 0. *
* * @author unascribed * @since JDK1.1 */

从类介绍看出的是,读取数据出现问题,有两个可能的原因,一个是读操作读取未读的原始数据,另一个是在流中的最后一个数据是序列化的对象。这两句话也看不出什么问题。网上查了一下有说要数据处理要同步。
无奈下,将存入的逻辑仔细debug好几十遍,出现这个错误的时候,存入的对象的序列化ID都是显示表明的,所以排除序列化ID不同造成的。最后,看存入的数据时,发现数据的数目总是变动,外部条件完全一致,获取的数据量总是不同,跟进去看到。使用了parallelStream,然后foreach,将元素加入到一个非线程安全的集合HashSet中,所以数据量一直变动。
为此我写了一段测试代码。

 public static void main(String[] args) throws InterruptedException {
        Collection<String> nameCollection = testParallelStream();
        System.out.println(nameCollection.size());
        nameCollection.forEach(name -> {
            System.out.println(name);
            System.out.println("sign");
        });
    }

    private static Collection<String> testParallelStream() {
        Set<String> nameSet = new HashSet<>();
        // Collection collection = new HashSet<>();
        Collection<String> collection = Collections.synchronizedSet(new HashSet<>());
        for (int i = 0; i < 1000; i++) {
            nameSet.add("cj" + i);
        }
        nameSet.parallelStream().forEach(name -> {
            collection.add(name);
        });
        System.out.println(collection.size());
        return collection;
    }

这个里面如果不使用Collections.synchronizedSet(new HashSet<>()),则会造成线程安全问题。本质上parallelStream是多线程处理,而HashSet是个非线程安全的集合,往里面放数据的时候,会出现数据丢失。

你可能感兴趣的:(Bug)