一. 问题的产生
什么是迭代稳定性?
对同个集合 高频的读操作, 可能会读到 修改一半的 状态结果 .例如: 在一次 put 10 个 值的时候, put到第5个的时候, 对集合进行了读操作, 此时读到的数据就不是一致性的, 读到了一半的数据.

这么说懂了吧兄嘚
Eurka源码中的具体表现
集合想必大家都知道, 不就是 Collection 包下的集合对吧, 稳定性又是从何说起呢? 下面咱们看一下Eurka中的源码
Eurka中 的 AbstractInstanceRegistry 中 针对 recentlyChangedQueue 最近修改队列 和 registry 注册表的迭代稳定性, 而选择用 读写锁做读写分离 .
//所有注册实例集合private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();//被修改过的实例集合,用于增量更新实例是读取private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue = new ConcurrentLinkedQueue<RecentlyChangedItem>();// 注册方法使用了读锁public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication){read.lock();...gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap); // 往注册表内添加实例...recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel)); //添加最近修改队列}// 取消实例的注册使用读锁protected boolean internalCancel(String appName, String id, boolean isReplication) {read.lock();Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);Lease<InstanceInfo> leaseToCancel = null;if (gMap != null) {leaseToCancel = gMap.remove(id); // 这里开始去注册表中移除 实例}.....recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel)); //添加最近修改队列}//更新实例的状态。使用读锁public boolean statusUpdate(String appName, String id,InstanceStatus newStatus, String lastDirtyTimestamp,boolean isReplication) {read.lock();Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);Lease<InstanceInfo> lease = null;if (gMap != null) {lease = gMap.get(id); //获取要修改的实例}....lease.serviceUp(); // 对要修改的实例进行某个状态的修改....recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel)); //添加最近修改队列}// 下线状态赋值给实例public boolean deleteStatusOverride(String appName, String id,InstanceStatus newStatus,String lastDirtyTimestamp,boolean isReplication) {read.lock();// 对实例进行状态修改 , 修改为下线状态recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel)); //添加最近修改队列}//获取所有 增量实例Applications (这里是用的写锁)public Applications getApplicationDeltas(){write.lock();Iterator<RecentlyChangedItem> iter = this.recentlyChangedQueue.iterator(); //返回最近改变的队列, 即为增量的实例}
上述代码中我们可以看到. Eurka 使用了读/写锁的添加方式就是为了解决两个共享集合 recentlyChangedQueue 与注册表 registry 的“集合迭代稳定性”问题。(当然细心的你可能会发现, 为啥这里 读的地方用了写锁, 写的地方使用了读锁呢? 这里咱先留个小预告, 请期待下一篇.)
二 . 解决思路
读写分离, 例如 有一个 OnlyreadMap 和 readwirdMap 进行读写分离, OnlyreadMap 定时的从 readwirdMap 中更新数据.
当然, 如果在定时更新的时候也发生了读操作, 那么此时可以用读写锁进行限制
读写锁, 做读写分离 rock.wird(); rock.read();
三 . 示例
1. 读写锁: 就是在更新前加入 做读写分离 rock.wird(); rock.read(); 上面代码已经演示了, 这里咱就不写了
2. 读写分离
//被修改过的实例集合,用于增量更新实例是读取private ConcurrentLinkedQueue<Object> map = new ConcurrentLinkedQueue<>();public ConcurrentLinkedQueue getMap(){return this.map;}public void uploadMap(Object o){ConcurrentLinkedQueue copyMap = new ConcurrentLinkedQueue();copyMap.addAll(map); //将当前的map 拷贝一份for ( o in copyMap){// dosomething... 开始遍历迭代或者一些耗时较长的操作}this.map = copyMap; // 修改完成后直接替换 最新的 集合}
四 . 总结
对于多个地方都会修改到的集合, 建议加上读写锁, 进行读写分离. 必要的话也可以加上 synchronized 或者JUC包下线程安全的集合. 进行读写
直接指向替换是一个非常快的操作, 能快速的将一个集合"更新"变成另外一个集合, 可以利用这一点, 对集合进行快速的改变 (相比起你一个一个去迭代去改, 修改中的状态会少很多对吧).

今天七夕, 如果你看到这里, 能不能给个关注呢

点个赞 👍 再走也行呐




