事情的起因是因爲我要在一個定時任務的某處將數據入庫,一個是批量入庫,之後再是非批量的普通方式入庫,僞代碼如下:
for(int i=0; i<n; i++) {
//批量入庫
pools.execute(new BatchSaveThread(list));//list爲map集合
//普通入庫
pools.execute(new SaveThread(map));
}
這個定時任務是每隔1min就觸發的,頻率還蠻高。而且n大約是10,pools爲線程池,BatchSaveThread和SaveThread分別爲批量入庫的線程和普通入庫的線程。
問題現象:這個定時任務放到環境上跑,發現過個5-7min就會“卡死”,即:批量入庫或普通入庫都不能持續,普通入庫在持續最多7分鐘左右就不再進行了。debug發現普通入庫的線程會進入,但是就是沒法往下執行。
解決:其實可以從線程的角度考慮,之前想在定時任務當中,用線程池調配線程的方式異步地入庫,但由於批量入庫的步驟可能需要的時間較長,所以線程在此處耗費時間較多,以至於在本分鐘內沒能完成,卡在這導致普通入庫那一步也沒有完成。
把普通入庫和批量入庫分爲2個定時任務,一前一後的執行。普通入庫先執行,在那裏for循環中負責生成每次跟循環有關的list,並把他們放到內存中,之後,在晚些時候執行批量入庫,再在批量入庫的定時任務中for循環,取出每次的list,再針對該List入庫。爲進一步避免線程問題,可以擯棄異步入庫,直接取代原來的入庫線程而調用dao的相關入庫方法,,後來發現其實也並沒有消耗太多時間。
注:
1)內存存取list可以使用ServletContext的setAttribute(String key, Object obj)和getAttribute(key);取完可以removeAttribute(String key)減少內存佔用~~;key的設計可以帶上for循環裏的元素以及時間。
2)mysql中想要真正的executeBatch()起作用,要記得在jdbc的url上加上rewriteBatchedStatements=true才行,否則批量執行是不會起作用的。
改進之後,經過長時間測試發現,無論是數據量較大的批量入庫,還是普通入庫,都能平穩地運行下去了。
順便補充些小問題:
1.循環一個集合的前提是這個集合不爲null,雖然默認情況下是根本不用考慮的,但如果你是通過本例中ServletContext的getAttribute(String key)這種方式,或其他方式得到List——即不是通過常見的定義方式得到的List,在循環它之前,需要做容錯處理:if(list != null)再循環哦!
2.在一個定時任務類的屬性定義上不要直接帶上需要通過bean得到的其他bean類,否則會報空指針異常。比如我在一個定時任務類的的屬性上(方法外)這樣:
WebApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext();
或這樣:
Dao dao = bean.get(xxxx)
都會報NullPointerException。之所以不能在方法體外的類屬性上直接定義這些,是因爲:定時任務類也是被配置成bean的,在Spring容器啓動成功之前,整個Spring容器的環境是還未完全搭建成功的,而bean的這些屬性會在把它管理之前就掃描到,所以你不能在還未完全搭建Spring環境成功就使用Spring的上下文。——這點需注意,可以延伸到任何一個需要被管理的bean,不能在方法體外部直接使用Spring上下文。