我的比较器方法如何违反其一般合同?

How does my comparator method violate its general contract?

第三方包

import java.util.Collections;
import java.util.Comparator;
import org.joda.time.DateTime;

我的比较器

public static Comparator<Task> TASK_PRIORITY = new Comparator<Task>() {
    public int compare(Task task1, Task task2) {
        if (task1 == null && task2 == null) return 0;
        if (task1 == null) return +1; //null last
        if (task2 == null) return -1; //null last

        // Only consider retries after a task is retried 5+ times
        if (task1.getRetries() >= 5 || task2.getRetries() >= 5) {
            // Primary sort: retry count (ascending)
            int retriesCompare = Integer.compare(task1.getRetries(), task2.getRetries());
            if (retriesCompare != 0) return retriesCompare;
        }

        // Secondary sort: creation time (ascending, null first)
        int creationCompare = compareTimeNullFirst(task1.getCreationTime(), task2.getCreationTime());
        if (creationCompare != 0) return creationCompare;

        // Tertiary sort: load time (ascending, null last)
        int loadCompare = compareTimeNullLast(task1.getLoadTime(), task2.getLoadTime());
        if (loadCompare != 0) return loadCompare;

        return 0;
    }
};

private static int compareTimeNullLast(DateTime time1, DateTime time2) {
    if (time1 == null && time2 == null) return 0;
    if (time1 == null) return +1;
    if (time2 == null) return -1;
    if (time1.isBefore(time2) return -1;
    if (time1.isAfter(time2)) return +1;
    return 0;
}

private static int compareTimeNullFirst(DateTime time1, DateTime time2) {
    if (time1 == null && time2 == null) return 0;
    if (time1 == null) return -1;
    if (time2 == null) return +1;
    if (time1.isBefore(time2) return -1;
    if (time1.isAfter(time2)) return +1;
    return 0;
}

使用我的比较器

//tasks is a List<Task>
Collections.sort(tasks, TASK_PRIORITY);

我的问题

我有时 Comparison method violates its general contract! 得到 IllegalArgumentException。我可以持续使用实时数据抛出此异常 运行 足够长的时间,但我不确定如何解决问题的实际原因。

我的问题

我的比较器有什么问题? (具体来说,我违反了合同的哪一部分?)如何在不掩盖异常的情况下修复它?

备注

让我们从头说起。我对您的代码的阅读是您的 Comparator 的逻辑是合理的。 (我会避免使用 null TaskDateTime 值,但这与您的问题无关。)

可能导致此异常的另一件事是 compare 方法给出不一致的结果,因为 Task objects 正在更改。实际上,看起来(至少)重试次数的改变在语义上是有意义的。如果有另一个线程正在更改 Task 可以影响排序的字段......而当前线程正在排序......那可能 IllegalArgumentException

(比较合同的一部分是,当您对 collection 进行排序时,成对排序不会改变。)

然后你这样说:

I use ImmutableSet.copyOf to copy the list before sorting, and I do that under a read lock in java.util.concurrent.locks.ReadWriteLock.

复制 collection 不会复制 collection 的元素。它是一个浅拷贝。所以你最终会得到两个包含相同 objects 的 collection。如果另一个线程改变了 objects 中的任何一个(例如,通过增加重试次数),这可能会改变 objects.

的顺序

锁定确保您拥有一致的副本,但这不是问题所在。

解决方法是什么?我能想到一对:

  1. 您可以在复制和排序时锁定某些内容以阻止对 collection 和元素 objects 的所有更新。

  2. 你可以deep-copy收集;即创建一个包含原始 collection.

  3. 元素副本的新 collection
  4. 您可以创建 light-weight objects,其中包含与排序相关的 Task objects 字段的快照;例如

    public class Key implements Comparable<Key> {
        private int retries;
        private DateTime creation;
        private DateTime load;
        private Task task;
    
        public Key(Task task) {
            this.task = task;
            this.retries = task.getRetryCount();
            ...
        }
    
        public int compareTo(Key other) {
            // compare using retries, creation, load
        }
    }
    

    这有一个潜在的好处,就是你复制的信息更少,你可以从 Key objects 的排序 collection 到原来的 Task objects.

请注意,所有这些备选方案都比您目前正在做的要慢。我认为没有办法避免这种情况。