我的要求是找出具有给定条件的所有匹配对象。因此,我做了一个创建自定义谓词的操作,并通过了匹配条件和应该与对象匹配的源ID。如果条件匹配,那么我在内部填充地图以保存详细信息。
并且当流完成时,此映射将具有所有匹配条件的详细信息。因此,我将此地图用于以后的计算。在大多数情况下,它可以工作,但会随机开始失败。
我的自定义谓词是-
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;
}
}
我在符合条件时添加了日志语句,并且我们更新了地图,有时地图只有1个元素(即使我期望是2个元素),有时也只有0个元素。
2019-11-24 09:31:52.574 PST调试ForkJoinPool.commonPool-worker-5 IdentifierMapPredicate:52-identifierMap:{}
2019-11-24 09:31:52.574 PST调试ForkJoinPool.commonPool-worker-7 IdentifierMapPredicate:52 -identifierMap:{}
以上实现有什么问题吗?将其更改为ConcurrentHashMap应该会有所帮助吗?
在大多数情况下,它可以正常工作,但会随机失败
是的,经典race condition。
您正在多线程上下文中使用IdentifierMapPredicate
。如果您同时访问IdentifierMapPredicate.test,线程将同时使用identifierMap
,这不是线程安全的。
如果您还为HashSet使用线程保存替代方案,就像@Ivan指出的那样,看起来ConcurrentHashMap将解决此问题。另外,您可以使用test
关键字修改方法synchronized
,但这将给您带来更少的吞吐量/更多的锁定。但是另一方面,也减少了头痛,这是一个折衷,如果没有严格的性能要求,我很乐意接受较少的头痛。
顺便说一句。现在,您可以使用Map。computeIfAbsent创建新的ids