java 、HashMap 和單例

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

一、復現問題。從新走了一遍出問題的過程,發現問題的確存在。說明這個問題不是偶然發生。數據庫

二、看日誌。肯定問題是必然發生以後,開始查看日誌,發現日誌中有問題的系統狀態一直不正常。一直處於任務過時的狀態。一個系統對應一個任務,任務過時以後,系統就處於卡死狀態。系統的邏輯是這樣的:當啓動系統的時候,會發起多個請求,每一個請求會產生一個任務,同時將這些任務寫到緩存(HashMap)和數據庫。任務的狀態(包括數據庫和緩存)會隨着任務的進度而發生改變。緩存

任務過時意味着該任務已經執行完畢或者歷來沒有這個任務。安全

若是說任務已經執行完畢致使這個問題的話,這個是不可能的。由於對於每一個任務,當他執行成功或者失敗時,垃圾回收器會在15分鐘後對任務進行清理。事實上,當咱們一開啓系統時,就觀察到該系統對應的任務在數據庫中存在,可是在緩存中卻不存在!就是說,當咱們從HashMap 中獲取相應的任務時,獲取到的值是不存在的!爲何獲取到的值會不存在呢?這可能有兩種緣由:併發

(1)任務根本就沒有寫入緩存;高併發

(2)任務寫入緩存後很快被清理掉了;spa

可是根據以上的分析,任務被很快清理掉是不可能的。由於至少得在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.

相關文章
相關標籤/搜索