整個網站訪問不了,后台日志內存溢出,提出了個致命單,找到問題后,整理成了案例,供培訓使用。
原因:
流量統計FlowUtil類使用兩個static的List來裝載流量信息實體bean。
用戶每次點擊都會將一個產生一個bean並加入到第一個List1中,當List1里的bean到一定數量時(可在后台配置緩存大小),List1將所有的實體bean復制到 List2中,然后List1清空繼續接收新的bean,這時List2開啟一個新線程異步去將bean插入數據庫,然后清空。
Method add(bean){
list1.add(bean);
if (list1.size() >= cacheSize)
{
List2.addAll(list1);
list1.clear();
new Thread({
try
{
insertDB(list2);
list2.clear();
}
catch (ApplicationException e)
{
}
}).start();
}
}
整個操作沒有做同步鎖定,如果並發量大,List2還沒完成插入數據庫的操作,List1又將新接收的bean全部加入到List2中,又發起一個新線程去插數據庫,如果這個線程跑在之前那個線程前,因為這時List2之前的bean是沒有被清空的,再插入數據庫的時候,id就會重復,就會拋出違反唯一性約束異常,就不會執行下一行清空List2的代碼,直接結束了。以后無論如何,每次操作都會拋出違反唯一性約束異常了,這個時候List2就會不斷地接收List1傳進來的bean,直到內存溢出。
分析:如果在try-catach后的finally里去clear清空list2,不會出現這種現象,但也有線程安全問題。這段代碼存在兩個問題,一個是線程安全,一個就是try-catach段代碼,沒考慮到異常的情況,在try里每段代碼都應考慮如果執行到這段會不會有影響,如果有就在finnally里執行。
解決方案:同步數據轉交那段代碼,並不再使用static的List2,List1每次轉交都給一個全新的List去執行插數據庫。
Method add(bean){
list1.add(bean);
if (list1.size() >= cacheSize)
{
List list2 = new List();
Synchronized{
list2.addAll(list1);
list1.clear();
}
new Thread({
try
{
insertDB(list2);
}
catch (ApplicationException e)
{
}
}).start();
}
}
由於每次都使用一個新的list2,也就不用再清空了。