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

Java Hazelcast IMap计算方法中的无限循环,java,hazelcast,Java,Hazelcast,我尝试使用Set接口作为hazelcastIMap实例的值,当我运行测试时,我发现测试挂在ConcurrentMap\compute方法中 为什么在此代码中使用hazelcastIMap时会有无限循环: import com.hazelcast.config.Config; import com.hazelcast.config.MapConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.IMap; imp

我尝试使用
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);
        }
    }
}
import com.hazelcast.config.config;
导入com.hazelcast.config.MapConfig;
导入com.hazelcast.core.hazelcast;
导入com.hazelcast.core.IMap;
导入java.io.Serializable;
导入java.util.*;
公共班机{
公共静态void main(字符串[]args){
IMap store=Hazelcast.newHazelcastInstance(
新配置().addMapConfig(新的MapConfig(“存储”))
).getMap(“商店”);
store.compute(“用户”,(k,值)->{
HashSet newValues=Objects.isNull(值)?new HashSet():new HashSet(值);
添加(新StringWrapper(“用户”);
返回新值;
});
store.compute(“用户”,(k,值)->{
HashSet newValues=Objects.isNull(值)?new HashSet():new HashSet(值);
添加(新StringWrapper(“用户”);
返回新值;
});
System.out.println(store.keySet());
}
//数据类
公共静态类StringWrapper实现可序列化{
字符串值;
公共StringWrapper(){}
公共StringWrapper(字符串值){
这个值=值;
}
公共字符串getValue(){
返回值;
}
公共void设置值(字符串值){
这个值=值;
}
@凌驾
公共布尔等于(对象o){
如果(this==o)返回true;
如果(o==null | | getClass()!=o.getClass())返回false;
如果(!super.equals(o))返回false;
StringWrapper值=(StringWrapper)o;
返回Objects.equals(this.value,value.value);
}
@凌驾
公共int hashCode(){
返回Objects.hash(super.hashCode(),value);
}
}
}
Hazelcast:
3.9.3
Java:
build1.8.0_161-b12

操作系统:
macOS High Sierra 10.13.3

@Alykoff我根据上述示例和ArrayList版本复制了该问题,该版本被报告为github问题:

有两个不同的问题:

1-当使用HashSet时,问题是Java如何反序列化HashSet/ArrayList(collections)以及
compute
方法如何工作。在
compute
方法内部(由于Hazelcast符合Java 6,没有
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 replace方法。IMap检查当前值是否等于用户提供的值。但是由于Java序列化
优化
,检查失败。请检查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);
但是在没有初始容量的情况下创建的
哈希集的默认容量为16,而反序列化的哈希集的初始容量为1。这会改变序列化,索引51包含当前容量&在反序列化对象时,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));

Collection newValues=Objects.isNull(值)?新建ArrayList():新建ArrayList(值);
这个
HashSet
案例似乎是JDK的问题,而不是优化。我不知道这些情况中有任何一个可以在Hazelcast中解决/修复,除非Hazalcast重写了
HashXXX
集合序列化并重写了
compute
方法

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