不可变对象即使用对外可见的状态不可变的对象,例如,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多线程编程(设计模式篇),大家可以去研读下,我这里也是做了很粗浅的介绍,有什么不对的地方,欢迎指正。