用不可变类防止并发问题的思路进行实战

1 基于不可变模式解决并发问题

1.1 业务介绍

  • 需求:发短信给客户
  • 问题:短信厂商众多,需要实时择优
  • 业务流程
    • 定时进行短信厂商PK,最终决定一家服务商。
    • 短信网关,通过这个被选定的服务商,发送短信。

1.2 服务商代码

    public class SmsInfo{
        // text url
        private String url;

        // the max length of a text
        private Long maxSizeInBytes;

        public SmsInfo(String url, Long maxSizeInBytes){
            this.url = url;
            this.maxSizeInBytes = maxSizeInBytes;
        }

        set\get 方法省略
    }

1.3 PK短信服务商代码

    public class SmsRouter{
        // 短信网关对象,通过volatile修饰保证其他线程的可见性
        private static volatile SmsRouter instance = new SmsRouter();

        // 记录服务商优劣
        private final Map<Integer, SmsInfo> smsInfoRouterMap;

        public SmsRouter{
            this.smsInfoRouterMap = this.loadSmsInfoRouterMapFromDb();
        }

        private Map<Integer, SmsInfo> loadSmsInfoRouterMapFromDb(){
            Map<Integer, SmsInfo> routerMap = new HashMap<>();
            routerMap.put(1, new SmsInfo("https://www.aliyun.com", 180L));
            routerMap.put(2, new SmsInfo("https://www.cloud.tencent.com", 181L));
            routerMap.put(3, new SmsInfo("https://www.cloud.baidu.com", 182L));
        }
    }

在构造函数中调用了loadSmsInfoRouterMapFromDb方法,把数据库中的数据(此处是模拟的)加载到内存之中。

其中这个Map的Key就是排名。

1.4 短信服务商更新数据代码

    public void changeRouteInfo(){
        Map<Integer, SmsInfo> smsInfoRouteMap = smsRouter.getSmsInfoRouteMap();
        SmsInfo smsInfo = smsInfoRouteMap.get(3);
        smsInfo.setUrl("https://www.jiguang.cn");
        smsInfo.setMaxSizeInBytes(183L);
    }

1.5 问题出现

注意,changeRouteInfo()并不是一个原子操作。

因此完全可能出现,刚刚设置了新的URL,还没来得及设置大小,结果别的线程就抢先一步访问数据库,读取数据。

结果读取到了新的url和旧的大小。

1.6 改造

思路就是把SmsInfo改造成一个不可变类。

    // 把smsinfo改造成不可变类
    public class SmsInfo{
        private final String url;
        private final Long maxSizeInBytes;
        private final Long id;

        public SmsInfo(Long id, String url, Long maxSizeInBytes){
            this.id = id;
            this.url = url;
            this.maxSizeInBytes = maxSizeInBytes;
        }

        public SmsInfo(SmsInfo smsInfo){
            this.id = smsInfo.getId();
            this.url = smsInfo.getUrl();
            this.maxSizeInBytes = smsinfo.getMaxSizeInBytes();
        }
    }
    // 更改短信供应商信息代码
    public void changeRouteInfo(){
        // 进行防御性复制
        public Map<Integer, SmsInfo> getSmsInfoRouteMap(){
            return Collections.unmodifiableMap(deepCopy(smsInfoRouteMap));
        }

        private Map<Integer, SmsInfo> deepCopy(Map<Integer, SmsInfo> smsInfoRouteMap){
            Map<Integer, SmsInfo> resultMap = new HashMap<>(smsInfoRouteMap.size());
            for (Map.Entry<Integer, Sminfo> entry : smsInfoRouteMap){
                resultMap.put(entry.getKey(), new SmsInfo(entry.getValue()));
            }
            return resultMap;
        }
    }
    public class SmsRouter{
        private static volatile SmsRouter instance = new SmsRouter();

        public static SmsRouter getInstance(){
            return instance;
        }

        public static void setInstance(SmsRouter newInstance){
            instance = newInstance;
        }

        public static void setInstance(SmsRouter newInstance){
            instance = newInstance;
        }
    }
    // 更新短信服务商代码
    public void changeRunteInfo(){
        updateSmsRouteInfoLists();
        SmsRouter.setInstance(new SmsRouter())
    }
posted @ 2021-06-09 15:12  Yiyang_Cai  阅读(75)  评论(0编辑  收藏  举报