java 、HashMap 和單例

前段時間在項目中遇到一個問題。當多個系統同時運行時,大部分系統可以良好運轉,部分卻卡死在了啓動界面。如下是我解決該問題的步驟和總結:數據庫

 
一、復現問題。從新走了一遍出問題的過程,發現問題的確存在。說明這個問題不是偶然發生。
二、看日誌。肯定問題是必然發生以後,開始查看日誌,發現日誌中有問題的系統狀態一直不正常。一直處於任務過時的狀態。一個系統對應一個任務,任務過時以後,系統就處於卡死狀態。系統的邏輯是這樣的:當啓動系統的時候,會發起多個請求,每一個請求會產生一個任務,同時將這些任務寫到緩存(HashMap)和數據庫。任務的狀態(包括數據庫和緩存)會隨着任務的進度而發生改變。
 
 
任務過時意味着該任務已經執行完畢或者歷來沒有這個任務。
若是說任務已經執行完畢致使這個問題的話,這個是不可能的。由於對於每一個任務,當他執行成功或者失敗時,垃圾回收器會在15分鐘後對任務進行清理。事實上,當咱們一開啓系統時,就觀察到該系統對應的任務在數據庫中存在,可是在緩存中卻不存在!就是說,當咱們從HashMap 中獲取相應的任務時,獲取到的值是不存在的!爲何獲取到的值會不存在呢?這可能有兩種緣由:
(1)任務根本就沒有寫入緩存;
(2)任務寫入緩存後很快被清理掉了;
可是根據以上的分析,任務被很快清理掉是不可能的。由於至少得在15分鐘以後,才能清理。那就只有第一種可能了:任務根本沒有寫入緩存!
 
開始着手看代碼。發現寫入緩存的關鍵一行代碼:
MyMap. getInstance().put(  taskId "hello"  );
 
繼續跟蹤MyMap,主要的類相關內容以下:
 
public   class  MyMap {
     
       private  Map<Integer, Object>  map  =  new  HashMap<Integer, Object>();
       private  Object  lock  =  new  Object();
     
       private   static  MyMap  instance  new  MyMap();
       private  MyMap(){}
       public   static  MyMap getInstance() {
            if  ( instance  ==  null ) {
                instance   new  MyMap();
           }
            return   instance  ;
      }
       public   void  put(Integer taskId, String name) {
            synchronized  ( lock  ) {
                map .put(taskId, name);
           }
      }
     
       public  Map<Integer, Object> getMap() {
            return   map  ;
      }
 
}
 
 
該類使用單例模式,使用HashMap來保存全部的任務。每次執行一個任務,都會將這個任務寫入緩存。而後根據taskId獲取相應的任務。這段代碼看起來沒有多大問題。
 
可是在高併發的狀況下,這個單例是不安全的:
public   static  MyMap getInstance() {
            if  ( instance  ==  null ) {
                instance   new  MyMap();
          }
            return   instance  ;
     }
 
在多個線程同時請求getInstance時,某個線程,判斷instance == null 爲true,會繼續執行instance = new MyMap(); 
 
這行代碼會先new MyMap(),在heap上分配內存空間,而後將instance 指向該內存地址。在instance 未指向該內存空間時,若是其餘線程也調用getInstance時,發現instance == null 爲真,也會執行new MyMap()。這時,不一樣的線程拿到的就不是同一個實例了。調用put後,會將不一樣的數據寫入到不一樣對象對應的map中。因此咱們拿到的實例有多是全部線程共享的實例,也有多是某些線程共享的實例,固然咱們就只能獲取到部分數據,另外的數據就丟失了。或者說數據依然在某個內存中,可是咱們丟失了指向該數據的引用。因此部分任務就這麼丟失了,致使系統處於卡死狀態。
 
如何來處理這種不安全的單例呢?
使用兩種方式能夠解決:
 
(1)給getInstance()方法添加關鍵字synchronized,保證當前只有一個線程執行該方法。
 
public synchronized   static  MyMap getInstance() {
            if  ( instance  ==  null ) {
                instance   new  MyMap();
           }
            return   instance  ;
 }
(2)
private   static  MyMap  instance  =  new  MyMap();
private  MyMap(){}
public   static  MyMap getInstance() {
            return   instance  ;
}
 
第一種方式使用效率較低。第二種方式在類加載時便生成對象。沒有使用類的延遲加載。
另外還有兩種方式能夠實現:內部靜態類和雙重校驗鎖(暫且不討論)。
 
經過這兩種方式,便可以解決單例模式的線程安全問題。同時,爲了提升效率,將緩存從HashMap改成ConcurrentHashMap.
相關文章
相關標籤/搜索