記一次bug fix:比較器錯誤

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」。

二、這個問題能不能避免?

能。原代碼是有邏輯衝突的,想到就能避免。

相關文章
相關標籤/搜索