JodaTime比较方法违反通用契约异常问题排查咨询
问题描述
在多线程环境中,timeCache可能被其他线程修改,待排序的TimeDTO列表包含50000+元素,getTime()方法返回org.joda.time.DateTime类型对象。使用如下Java方法排序时,偶尔抛出java.lang.IllegalArgumentException: Comparison method violates its general contract!异常:
private Collection<TimeDTO> sortTimes(Choice choice) { final List<TimeDTO> times = new ArrayList<>(timeCache.getAll(choice)); Collections.sort(times, new Comparator<TimeDTO>() { @Override public int compare(final TimeDTO o1, final TimeDTO o2) { if (o1.getTime() == null) { return -1; } if (o2.getTime() == null) { return 1; } return o1.getTime().compareTo(o2.getTime()); } }); return times; }
异常栈信息:
Caused by: java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeHi(TimSort.java:899) ~[na:1.8.0_301] at java.util.TimSort.mergeAt(TimSort.java:516) ~[na:1.8.0_301] at java.util.TimSort.mergeCollapse(TimSort.java:439) ~[na:1.8.0_301] at java.util.TimSort.sort(TimSort.java:245) ~[na:1.8.0_301] at java.util.Arrays.sort(Arrays.java:1512) ~[na:1.8.0_301] at java.util.ArrayList.sort(ArrayList.java:1464) ~[na:1.8.0_301] at java.util.Collections.sort(Collections.java:177) ~[na:1.8.0_301] at ****.ServiceImpl.sortTimes(ServiceImpl.java:100)
异常原因分析
比较器违反排序契约的核心规则:
当前比较器逻辑中,如果两个TimeDTO的getTime()都为null,调用compare(o1, o2)会返回-1,调用compare(o2, o1)同样返回-1。这直接违反了比较器的对称性(若a < b则必须b > a)与传递性(若a = b且b = c则a = c)。Java 7及以上版本默认使用的TimSort算法会主动校验排序契约,当列表中存在多个时间为null的元素时,尤其是元素数量超过5万、触发TimSort的合并逻辑时,这种契约违反会被检测到并抛出异常。多线程环境下的并发修改问题:
timeCache.getAll(choice)返回的集合在构造ArrayList的过程中,可能被其他线程修改。比如某个TimeDTO的getTime()值在构造列表时是非null,但排序过程中被改为null;或者构造时为null,排序时被改为非null。这种元素状态的并发变更会导致比较过程出现前后矛盾的结果,破坏排序契约,进而触发异常。
内容的提问来源于stack exchange,提问作者Rómeó Nagy
相关产品推荐
相关产品推荐

