0

我正在java.util.concurrent.ConcurrentSkipListSet用比较器创建一个。我的比较器比较对象中的 Instant 字段以确定顺序。这是比较器:

public int compare(myObj first, myObj second) {
        if (first == null || first.getTime() == null) {
            return 1; 
        }
        if (second == null || second.getTime() == null) {
            return -1;
        }
        
        if ( first.getTime().isBefore(second.getTime()) {
            return -1;
        }
        else { 
            return 1;
        }
    }

当我使用上述比较器 5/10 次时,它陷入了无限循环。First 总是在 second 之后,所以程序总是到达 else 并返回 1,但是在某些运行中它只是循环,它到达 else 但只是卡在不断运行比较器......我在调试模式下运行时可以看到这一点以及添加额外日志记录时。为了澄清这两个对象在卡住时是不一样的,所以问题不在于尝试添加重复项。当我像这样切换 isBefore 到 isAfter 时,我没有得到那种行为,它每次都按预期运行。我的问题是为什么会发生这种情况?

    public int compare(myObj first, myObj second) {
        if (first == null || first.getTime() == null) {
            return 1; 
        }
        if (second == null || second.getTime() == null) {
            return -1;
        }
        
        if ( first.getTime().isAfter(second.getTime()) {
            return 1;
        }
        else { 
            return -1;
        }
    }

添加到列表实现(为简单起见)

我的并发跳过集逻辑非常简单,我使用上面的比较器创建集合,然后 myObj 瞬间启动到 now() + 预定的分钟数(硬编码所以这个计算不会失败)然后我的 obj 被添加到并发跳过集

private ConcurrentSkipListSet<MyObj> skipSetImpl= new ConcurrentSkipListSet<>(new MyObj.MyobjComp()); 

// added in a method, using Java Instant
foo.setTime( now.plus(60, ChronoUnit.SECONDS) ); 

this.skipSetImpl.add(foo); // add to concurrentSkipSet
4

1 回答 1

2

你没有履行合同Comparator.compare()。这是否是您所问问题的根源,如果没有您的ConcurrentSkipSet代码,我们无法判断,但很可能是。

如果两个MyObj都是null,一个是null,另一个有一个null瞬间,或者两者都有一个null瞬间,那么你的比较器无法决定它们的顺序。如果你的skip set调用compare(a, b)a应该最后来,但是如果下一个调用compare(b, a),突然b应该最后来。这会导致它循环吗?在任何情况下,比较器的不一致都不是一个好的属性,它肯定会导致其他使用Comparator.

compare方法的文档说:

实施者必须确保sgn(compare(x, y)) == -sgn(compare(y, x))所有xy. (这意味着当且仅当 compare(y, x) 抛出异常时 compare(x, y) 必须抛出异常。)

这是你正在破坏的部分。您的跳过集应该能够依靠您遵守合同。

你说:

它们不能与我的逻辑和程序的工作方式相等,它们永远不应该相等

如果是这样,您需要为任意两个决定一个订单MyObj。一种解决方案是禁止空值(如果发生 a 则抛出异常null)。另一个是确定空值是否相等。

定义将空值放在最后的比较器的一种简洁方法是通过Comparator.nullsLast()and Comparator.comparing()(从 Java 8 开始):

    Comparator<MyObj> comp = Comparator.nullsLast(Comparator.comparing(
            MyObj::getTime, Comparator.nullsLast(Instant::compareTo)));

Comparator.nullsLast()为您提供一个比较器,该比较器最后对空值进行排序,并使用作为参数提供的比较器比较非空对象。我也在使用两个参数comparing​(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator)。第一个参数,MyObj::getTime告诉它比较时刻,第二个参数是用于比较时刻的比较器——在这种情况下,再次将空值放在最后。生成的比较器确实履行了 的合同Comparator.comparing()。极端情况:它将null MyObj引用放在MyObj具有null瞬间的对象之前,这与比较器所做的不同。

当我推荐 Java 8+ 定义比较器的方式时,并不是因为它很简短。更多的是因为它更难出错,包括很难打破compare方法的约定。

链接: 文档Comparator.compare()

于 2020-07-22T04:01:43.097 回答