我的要求是找出所有符合给定条件的匹配对象。 因此,我所做的就是创建一个自定义谓词并传递匹配条件以及应与对象匹配的源 ID。如果条件匹配,那么我将在内部填充一个 map 来保存详细信息。
当流完成时,该 map 将包含所有匹配条件的详细信息。 所以我用这张图来进行后面的计算。 大多数时候它可以工作,但开始随机失败。
我的自定义谓词是 -
public class IdentifierMapPredicate<T extends Identifiable, V> implements Predicate<T> {
private static Logger logger = Logger
.getLogger();
private Collection<V> sourceIds;
private BiPredicate<T, V> matchingCondition;
private Map<V, Collection<Identifier>> identifierMap;
public Map<V, Collection<Identifier>> getIdentifierMap() {
return identifierMap;
}
public IdentifierMapPredicate(Collection<V> sourceIds,
BiPredicate<T, V> matchingCondition) {
this.sourceIds = sourceIds;
this.matchingCondition = matchingCondition;
identifierMap = new HashMap<>(sourceIds.size());
}
@Override
public boolean test(T t) {
for (V id : sourceIds) {
if (matchingCondition.test(t, id)) {
//logger.debug("t :{}, id :{}", t.getIdentifier(), id);
Collection<Identifier> ids = identifierMap.get(id);
if(ids == null){
ids = new HashSet<>();
identifierMap.put(id, ids);
}
ids.add(t.getIdentifier());
logger.debug("identifierMap :{}", identifierMap);
return true;
}
}
return false;
}
}
当条件匹配并且我们更新 map 时,我添加了日志语句,有时 map 只有 1 个元素(即使我期望 2 个),有时甚至是 0。
2019-11-24 09:31:52.574 PST 调试 ForkJoinPool.commonPool-worker-5 IdentifierMapPredicate:52 - 标识符映射:{}
2019-11-24 09:31:52.574 PST 调试 ForkJoinPool.commonPool-worker-7 IdentifierMapPredicate:52 -identifierMap :{}
上面的实现有什么问题吗? 将其更改为 ConcurrentHashMap 会有帮助吗?
最佳答案
Most of the time it works, but starts failing randomly
是的,经典race condition .
您正在多线程上下文中使用IdentifierMapPredicate
。如果您同时访问 IdentifierMapPredicate.test,线程也会同时使用 identifierMap
,这不是线程安全的。
看起来 ConcurrentHashMap 可以解决这个问题。或者,您可以使用 synchronized
关键字修改方法 test
,但这会给您带来更少的吞吐量/更多的锁定(我认为)。但另一方面,也减少了头痛,这是一种权衡,如果没有严格的性能要求,我很乐意接受减少头痛。只是我个人的喜好。
顺便说一句。现在你可以使用 map 。 computeIfAbsent创建新条目而不是
Collection<Identifier> ids = identifierMap.get(id);
if(ids == null){
ids = new HashSet<>();
identifierMap.put(id, ids);
}
关于java - 自定义谓词有时会返回随机结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59020638/