1. 程式人生 > 實用技巧 >記錄一次線上環境執行緒池使用後未關閉的記錄

記錄一次線上環境執行緒池使用後未關閉的記錄

下午時候運維的同學給我說 某個服務,執行緒池達到了2000 但是一直未回收,他們那邊開始頻繁報警了。然後用jstat匯出了一份檔案:

第一個執行緒池bing-master-order-1 佔用了 1511個執行緒且未回收,第二個111 個。

剛開始自己也是一頭霧水,看了下下面的 mysql/redis ,這個應該是執行緒池佔用個數。但是第一個 是什麼,找到專案 ,搜搜 bing-master-order 發現有下面的程式碼

            JSONObject jsonResult = JSONObject.parseObject(result);
            
if (jsonResult.get(Constants.CODE) != null && jsonResult.getInteger(Constants.CODE) == 0) { logger.info("子單繫結主單成功"); this.noticeAssign(orderNo); ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("bing-master-order-%d").daemon(true
).build()); Future<String> future = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { logger.info("=================非同步處理開始============" + jsonResult.getJSONObject(Constants.DATA)); JSONObject jsonData
= jsonResult.getJSONObject(Constants.DATA); try { if (jsonData != null && jsonData.get(Constants.MAIN_ORDER_NO) != null) { String newMainOrderNo = jsonData.getString("mainOrderNo"); MainOrderInterCity queryMain = interService.queryMainOrder(newMainOrderNo); logger.info("========查詢結果=======" + JSONObject.toJSONString(queryMain)); int code = 0; if (queryMain != null && queryMain.getId() > 0) { code = interService.updateMainOrderState(newMainOrderNo, 1, dispatcherPhone); } else { MainOrderInterCity main = new MainOrderInterCity(); main.setDriverId(driverId); main.setCreateTime(new Date()); main.setUpdateTime(new Date()); main.setMainName(routeName); main.setStatus(MainOrderInterCity.orderState.NOTSETOUT.getCode()); main.setMainOrderNo(newMainOrderNo); main.setOpePhone(dispatcherPhone); main.setMainTime(crossCityStartTime); code = interService.addMainOrderNo(main); } if (code > 0) { logger.info("=========子單繫結主單成功======="); return String.valueOf(code); } return String.valueOf(code); } } catch (Exception e) { logger.error("子單繫結異常=======" + e); String newMainOrderNo = jsonData.getString("mainOrderNo"); MainOrderInterCity queryMain = interService.queryMainOrder(newMainOrderNo); logger.info("補錄子單繫結開始====" + JSONObject.toJSONString(queryMain)); if (queryMain != null && queryMain.getId() > 0) { int code = interService.updateMainOrderState(newMainOrderNo, 1, dispatcherPhone); if (code > 0) { logger.info("=====補錄異常成功====="); } } } return "=============子單繫結異常=========="; } }); return AjaxResponse.success(null); } else { logger.info("子單指派主單返回資訊========" + jsonResult.toString()); return AjaxResponse.failMsg(jsonResult.getIntValue("code"), jsonResult.getString("msg")); } }

注意紅色部分,點開.damon的原始碼

/**
         * Sets the daemon flag for the new {@code BasicThreadFactory}. If this
         * flag is set to <b>true</b> the new thread factory will create daemon
         * threads.
         *
         * @param f the value of the daemon flag
         * @return a reference to this {@code Builder}
         */
        public Builder daemon(final boolean f) {
            daemonFlag = Boolean.valueOf(f);
            return this;
        }

如果為屬性設定為true,表示將會建立一個daemon 守護執行緒。眾所周知,垃圾回收的jvm就是守護程序,當jvm都是守護程序時候 jvm會自動退出,所以設定後一直沒有被回收。知道原因後修改為false。想了下自己當時為何設定為true而不是false,應該是使用阿里規範程式碼時候推薦的。之前自己用的是ExcuterService 建立的,但是阿里規約不推薦,原因如下:

Executors各個方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要問題是堆積的請求處理佇列可能會耗費非常大的記憶體,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要問題是執行緒數最大數是Integer.MAX_VALUE,可能會建立數量非常多的執行緒,甚至OOM。

當時就改成這樣的了。沒想到還是有風險,這次先這樣給解決了,下次注意。