4月1号收到兼职端的反馈,说进入“已分配“页面失败,显示系统错误。使用我本身帐号登陆,没法复现问题,可是使用兼职同窗提供的帐号进行测试,确实存在这个问题。java
首先查看日志,有这样一段异常信息:git
java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.TimSort.mergeLo(TimSort.java:773) ~[na:1.8.0_51] at java.util.TimSort.mergeAt(TimSort.java:510) ~[na:1.8.0_51] at java.util.TimSort.mergeForceCollapse(TimSort.java:453) ~[na:1.8.0_51] at java.util.TimSort.sort(TimSort.java:250) ~[na:1.8.0_51] at java.util.Arrays.sort(Arrays.java:1512) ~[na:1.8.0_51] at java.util.ArrayList.sort(ArrayList.java:1454) ~[na:1.8.0_51] at java.util.Collections.sort(Collections.java:175) ~[na:1.8.0_51] at xyz.hlj.pttms.service.TaskService.polymerizeTaskByRoom(TaskService.java:923) ~[pttms-server.jar!/:na]
感受很奇怪,这是一个历来没见过的异常。定位到代码看一下算法
Collections.sort(resList, new Comparator<Map<String, Object>>() { @Override public int compare(Map<String, Object> o1, Map<String, Object> o2) { String o1Room = String.valueOf(o1.get("room")); String o2Room = String.valueOf(o2.get("room")); // 纯数字寝室号排序,由小到大排序 if (RegexUtils.checkDigit(o1Room) && RegexUtils.checkDigit(o2Room)) { return new BigInteger(o1Room).compareTo(new BigInteger(o2Room)); } return o1Room.compareTo(o2Room); } });
抛出异常的地方是一个使用自定义比较器对集合进行排序的方法,逻辑也比较简单:若是两个比较对象都是纯数字,按照数字大小排;不然按字符串顺序排。 仔细看了一遍比较器的实现代码,发现这段代码没有彻底知足业务上的要求,可是并无看出异常的缘由在哪里。 只好再去查异常,找到了jdk的一个不兼容说明(这个很关键,看到它以前被其余信息误导过):express
Area: API: Utilities Synopsis: Updated sort behavior for Arrays and Collections may throw an IllegalArgumentException Description: The sorting algorithm used by java.util.Arrays.sort and (indirectly) by java.util.Collections.sort has been replaced. The new sort implementation may throw an IllegalArgumentException if it detects a Comparable that violates the Comparable contract. The previous implementation silently ignored such a situation. If the previous behavior is desired, you can use the new system property, java.util.Arrays.useLegacyMergeSort, to restore previous mergesort behavior. Nature of Incompatibility: behavioral RFE: 6804124less
这份说明的意思是JDK比较器的排序算法更换了实现方式,新的实如今自定义比较器违背比较规则的状况下有可能会抛出异常,原来的实现忽略了这个异常。 若是须要原实现,能够经过增长系统属性java.util.Arrays.useLegacyMergeSort 恢复使用原来的排序规则。 看到这个,就肯定了问题的大概缘由,是咱们的比较器违反了比较规则。可是由于是线上错误,因此暂不分析具体缘由,先按照文档给的方法快速解决问题。ide
在系统启动时,设置useLegacyMergeSort 属性为true函数
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); SpringApplication.run(PttmsServerApp.class, args);
从新发布线上环境,测试经过。测试
问题的缘由已经基本肯定在违反比较器规则了,可是具体规则是什么?又是怎么违反的呢?摘抄一段java8 compare方法的java doc,咱们就能够清楚的知道规则是什么:ui
Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.In the foregoing description, the notation sgn(expression) designates the mathematical signum function, which is defined to return one of -1, 0, or 1 according to whether the value ofexpression is negative, zero or positive. The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.) The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0. Finally, the implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z. It is generally the case, but not strictly required that (compare(x, y)==0) == (x.equals(y)). Generally speaking, any comparator that violates this condition should clearly indicate this fact. The recommended language is "Note: this comparator imposes orderings that are inconsistent with equals."this
对任意对象x,y,z必须知足: 一、sgn(compare(x, y)) == -sgn(compare(y, x)) 二、若是compare(x, y)>0,而且compare(y, z)>0, 意味着 compare(x, z)>0 三、若是compare(x, y)==0 意味着 sgn(compare(x, z))==sgn(compare(y, z))
符号函数sgn, x>0时,sgn(x)=1; x<0时,sgn(x)=-1; x=0时,sgn(x)0
那么,咱们是怎么违反规则的呢? 查出问题楼栋的全部宿舍名观察数据, 803,1711,66666666636,429,211,345,1813,353,815,820,1403,1702,1306,439,1618,1514,355,355,1528,1528,401,1603,1111,1040,211,803,603,352,803,603,1015,1603,1040,1418,1426,102,1418,5~216,1731,351,901,703,1113,805,1104,1007,888888888888888888,833,1407,636,704,652,812,102,1714,843,944,412,209,1611,1809,1418,410,1701,1404,351,5-427,740,812,1408,1531,1530,1828,809,1808,809,234
从中能够看到有5-42七、5~216这样的数据,Get。 说明一下问题,摘出3条数据803,1711和5-427分别记为x, y, z,有compare(x, y)<0, compare(y, z)<0,按照规则应该得出compare(x, z)<0,可是实际上compare(x, z)>0。
从新实现比较器方法: 两者都是数字,按数字大小排序 两者都不是数字,按字符串大小排序 一个是数字,一个不是数字,始终保持数字在前
一、为何说原来的实现并无彻底知足业务的需求?
原实现会排列出“2栋301,505,5-427,803,1711”这样的结果,实际须要的是“505,803,1711,2栋301,5-427”。
二、这个问题能不能避免?
能。原代码是有逻辑冲突的,想到就能避免。