问题
最近发现线上一段大半年都没有变动的代码报错。
错误栈如下:
1
2
3
4
5
6
7
|
Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:899)
at java.util.TimSort.mergeAt(TimSort.java:516)
at java.util.TimSort.mergeCollapse(TimSort.java:441)
at java.util.TimSort.sort(TimSort.java:245)
at java.util.Arrays.sort(Arrays.java:1438)
at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
|
通过日志定位代码发现为排序报错
1
2
3
4
5
6
|
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2 ? 1 : -1;
}
});
|
很奇怪这代码为什么会报错,之前的排序好像都是这样写的并没有问题。
原因
根据日志错误信息Comparison method violates its general
contract!
通过google查询发现是违反了排序规约,原来从java7开始针对排序进行了更严格的限制。
- sgn(compare(x, y)) == -sgn(compare(y, x))
- ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0
- compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z))
简单来说就是违反了自反性和传递性。
到这里问题的原因基本定位了,前不久将应用jdk6升级到了8,
那为什么大半年了才暴露出来。
扒了一下源码发现只有在排序元素大于32才会有机率出现此错误。
所以这也解释了为什么升级jdk8后大半年没有报错。
下面是一段测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public static void main(String[] args) throws Exception {
while (true) {
List<Integer> list = new ArrayList<>();
while (list.size() < 32) {
list.add(new Random().nextInt(100));
}
System.out.println("list:" + list);
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2 ? 1 : -1;
}
});
}
}
|
有兴趣的同学可以自已研究一下源码。
解决方案
1. 实现规约的限制
拿开的代码头来说,o1 == o2
和o1 = null, o2 = null
的case没考虑,所以导致违反了自反性和传递性。正确的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if(o1 == null && o2 == null) {
return 0;
}
if(o1 == null) {
return -1;
}
if(o2 == null) {
return 1;
}
if(o1 == o2) {
return 0;
}
return o1 > o2 ? 1 : -1;
}
});
|
2. 降级排序算法为java7之前的版本
代码里设置系统变量降级
1
|
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
|
jvm启动参数增加降级参数
1
|
-Djava.util.Arrays.useLegacyMergeSort=true
|
可根据自身的实际情况来选则降级方案,这只是临时修复方案,应尽量改为更为严谨的排序算法。