不可变对象模式

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值