不可變對象即使用對外可見的狀態不可變的對象,例如,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,也省去了使用鎖等同步手段的額外開銷。
使用不可變對象需要注意以下幾個問題:
- 被建模對象的狀態變更比較頻繁,這時,就不需要使用不可變的對象了。這個會造成jvm垃圾回收的負擔,影響內存。
- 真正不可變的對象是不存在的,所以,需要結合業務場景,使用等效的不可變對象。
- 對於外部訪問的數據,要做好防禦性複製,不然會被外部數據修改。例如,這段代碼Collections.unmodifiableMap(deepCopyMMCInfo(routeMap));
java類庫中,也有類似的代碼,比如CopyOnWriteArrayLIst,在每次寫數據的時候,也是新創建一個數據,對數據做搬移操作,替換對應的原數組。返回的數據,也使用了防禦性複製,具體代碼,感興趣的讀者,可以去研讀下,這裏就不做過多的分析了。
最後,本文參考java多線程編程(設計模式篇),大家可以去研讀下,我這裏也是做了很粗淺的介紹,有什麼不對的地方,歡迎指正。