Hazelcast IMap中的无限循环用于计算方法

时间:2018-03-10 21:52:38

标签: java hazelcast

我尝试使用Set接口作为hazelcast IMap实例的值,当我运行测试时,我发现测试挂在ConcurrentMap#compute方法中。

为什么在此代码中使用hazelcast IMap时会出现无限循环:

import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.IMap;

import java.io.Serializable;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        IMap<String, HashSet<StringWrapper>> store = Hazelcast.newHazelcastInstance(
                new Config().addMapConfig(new MapConfig("store"))
        ).getMap("store");
        store.compute("user", (k, value) -> {
            HashSet<StringWrapper> newValues = Objects.isNull(value) ? new HashSet<>() : new HashSet<>(value);
            newValues.add(new StringWrapper("user"));
            return newValues;
        });
        store.compute("user", (k, value) -> {
            HashSet<StringWrapper> newValues = Objects.isNull(value) ? new HashSet<>() : new HashSet<>(value);
            newValues.add(new StringWrapper("user"));
            return newValues;
        });

        System.out.println(store.keySet());
    }

    // Data class
    public static class StringWrapper implements Serializable {
        String value;

        public StringWrapper() {}

        public StringWrapper(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            if (!super.equals(o)) return false;
            StringWrapper value = (StringWrapper) o;
            return Objects.equals(this.value, value.value);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), value);
        }
    }
}

Hazelcast:3.9.3 Java的:build 1.8.0_161-b12 操作系统:macOS High Sierra 10.13.3

1 个答案:

答案 0 :(得分:3)

@Alykoff我根据上面的例子重现了这个问题。 ArrayList版本,报告为github问题:https://github.com/hazelcast/hazelcast/issues/12557

有两个单独的问题:

1 - 当使用HashSet时,问题是Java如何反序列化HashSet / ArrayList(集合)&amp; compute方法的工作原理。在compute方法内部(因为Hazelcast符合Java 6&amp;没有compute方法来覆盖,默认实现来自ConcurrentMap),这个块会导致无限循环:

// replace
if (replace(key, oldValue, newValue)) {
    // replaced as expected.
    return newValue;
}

// some other value replaced old value. try again.
oldValue = get(key);

replace方法调用IMap替换方法。 IMap检查当前值是否等于用户提供的值。但由于Java序列化optimization,检查失败。请检查HashSet.readObject方法。您将在反序列化HashSet时看到,因为元素大小已知,所以它会创建具有容量的内部HashMap:

// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
        HashMap.MAXIMUM_CAPACITY);

但是,在没有初始容量的情况下创建的HashSet的默认容量为16,而反序列化的容量的初始容量为1.这会更改序列化,索引51包含当前容量&amp;似乎JDK在反序列化对象时根据大小重新计算它以最小化大小。

请参阅以下示例:

HazelcastInstance hz = Hazelcast.newHazelcastInstance();

IMap<String, Collection<String>> store = instance.getMap("store");

Collection<String> val = new HashSet<>();
val.add("a");

store.put("a", val);

Collection<String> oldVal = store.get("a");

byte[] dataOld = ((HazelcastInstanceProxy) hz).getSerializationService().toBytes(oldVal);
byte[] dataNew = ((HazelcastInstanceProxy) hz).getSerializationService().toBytes(val);

System.out.println(Arrays.equals(dataNew, dataOld));

此代码打印false。但是如果使用初始大小HashSet创建1,则两个字节数组都相等。在你的情况下,你不会获得无限循环。

2 - 使用ArrayList或任何其他收藏时,您指出了另一个问题。由于在compute中实施了ConcurrentMap方法,当您将旧值分配给newValue&amp;添加一个新元素,实际修改oldValue因此导致替换方法失败。但是当您将代码更改为new ArrayList(value)时,现在您正在创建new ArrayList&amp; value集合未被修改。如果您不想修改原始集合,最好在使用之前包装集合。由于我解释过第一个问题,如果您使用尺寸HashSet创建1,则相同。

所以在你的情况下,你应该使用

Collection<String> newValues = Objects.isNull(value) ? new HashSet<>(1) : new HashSet<>(value);

Collection<String> newValues = Objects.isNull(value) ? new ArrayList<>() : new ArrayList<>(value);

HashSet案例似乎是JDK问题,而不是优化问题。我不知道这些案例中的任何一个都可以在Hazelcast中解决/修复,除非Hazalcast优先于HashXXX集合序列化&amp;覆盖compute方法。