不可變對象模式

  不可變對象即使用對外可見的狀態不可變的對象,例如,java中的String和Integer對象,使得被共享的對象具有天生的線程安全,而無需額外的使用鎖等方式,增加開銷

下面就看看不可變的對象,在多線程的中怎麼具體使用。

  某彩信網關係統下發給用戶消息時,需要根據用戶的手機號的前綴選擇對應的彩信中心,選擇彩信中心的這個過程,成爲路由,對於這個路由表來說,是不經常變化的,或者說變化的頻率不大,爲了線程安全,這裏不想使用鎖的方式,增加開銷,所以將路由表作爲不可變的對象。

直接上代碼:

/**
 * 彩信中心路由規則管理器
 */
public final class MMSCRouter {

    // 用volatile修飾,保證多線程環境下該變量的可變量
    private static volatile MMSCRouter instance = new MMSCRouter();
    // 維護手機號碼前綴到彩信中心之間的映射關係
    private final Map<String, MMSCInfo> routeMap;

    public MMSCRouter() {
        this.routeMap = findRouteMapFromDB();
    }

    private static Map<String, MMSCInfo> findRouteMapFromDB() {
        Map<String, MMSCInfo> map = new HashMap<>();
        return map;
    }

    public static MMSCRouter getInstance() {
        return instance;
    }

    /**
     * 根據手機號前綴獲取彩信中心的地址
     * @param mobilePhonePrefix
     * @return
     */
    public MMSCInfo getMMSC(String mobilePhonePrefix) {
        return this.routeMap.get(mobilePhonePrefix);
    }

    /**
     * 將當前MMSCRouter的實例替換爲傳入的新實例
     * @param newInstance
     */
    public static void setInstance(MMSCRouter newInstance) {
        instance = newInstance;
    }

    /**
     * 拷貝映射關係
     * @param m
     * @return
     */
    private static Map<String, MMSCInfo> deepCopyMMCInfo(Map<String, MMSCInfo> m) {
        Map<String, MMSCInfo> result = new HashMap<>();
        m.forEach((k,v) -> {
            result.put(k,new MMSCInfo(m.get(k)));
        });
        return result;
    }

    public Map<String, MMSCInfo> getRouteMap() {
        // 返回不可變的映射路由表
        return Collections.unmodifiableMap(deepCopyMMCInfo(routeMap));
    }
}
/**
 * 不可變的對象 ---> 彩信中心
 */
public final class MMSCInfo {

    /**
     * 設備編號
     */
    private final String deviceID;

    /**
     * 彩信中心的url
     */
    private final String url;

    /**
     * 該彩信中心允許的最大附件大小
     */
    private final int    maxAttachmentSizeInBytes;

    public MMSCInfo(String deviceID, String url,int maxAttachmentSizeInBytes) {
        this.deviceID = deviceID;
        this.url = url;
        this.maxAttachmentSizeInBytes = maxAttachmentSizeInBytes;
    }

    public MMSCInfo(MMSCInfo prototype) {
        this.deviceID = prototype.deviceID;
        this.url = prototype.url;
        this.maxAttachmentSizeInBytes = prototype.maxAttachmentSizeInBytes;
    }

    public String getDeviceID() {
        return deviceID;
    }

    public String getUrl() {
        return url;
    }

    public int getMaxAttachmentSizeInBytes() {
        return maxAttachmentSizeInBytes;
    }
}
public class OperatorAgent extends Thread {

    @Override
    public void run() {
        boolean isUpdateRouter = false;
        String updateTableName = null;
        while (true) {
            /**
             *  處理外部請求信息,解析數據
             *  同時替換對應的局部變量
             */
            if (isUpdateRouter) {
                if ("MMSCInfo".equalsIgnoreCase(updateTableName)) {
                    //會調用MMSCRouter 的setInstance方法,替換對應的路由表對象
                    MMSCRouter.setInstance(new MMSCRouter());
                }
            }

        }
    }
}

  上述代碼中,OperatorAgent 是一個參與者的實例,負責解析數據,執行修改路由表等操作,而MMSCRouter ,MMSCInfo是一個不可變的對象實例,通過使用不可變的對象,可以應對路由表,彩信中心這些不是變動不是非常頻繁的場景case,也省去了使用鎖等同步手段的額外開銷。

使用不可變對象需要注意以下幾個問題:

  1. 被建模對象的狀態變更比較頻繁,這時,就不需要使用不可變的對象了。這個會造成jvm垃圾回收的負擔,影響內存。
  2. 真正不可變的對象是不存在的,所以,需要結合業務場景,使用等效的不可變對象。
  3. 對於外部訪問的數據,要做好防禦性複製,不然會被外部數據修改。例如,這段代碼Collections.unmodifiableMap(deepCopyMMCInfo(routeMap));

  java類庫中,也有類似的代碼,比如CopyOnWriteArrayLIst,在每次寫數據的時候,也是新創建一個數據,對數據做搬移操作,替換對應的原數組。返回的數據,也使用了防禦性複製,具體代碼,感興趣的讀者,可以去研讀下,這裏就不做過多的分析了。

  最後,本文參考java多線程編程(設計模式篇),大家可以去研讀下,我這裏也是做了很粗淺的介紹,有什麼不對的地方,歡迎指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章