在spring源码中,有一段关于注册bean的处理:
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
// 没明白这一行的意义
Set updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
// 为什么不直接使用remove()去删除元素呢?而且复制->删除->替换?
// this.manualSingletonNames.remove(beanName);
}
}
}
为什么不直接使用remove()去删除元素呢?而且复制->删除->替换?
求解
这段代码中的注释已经解释了这一行代码的作用:为了稳定迭代,不能再修改启动时的集合元素了。换句话说,如果我们在遍历这个集合时删除了一个元素,就会抛出ConcurrentModificationException异常,因为集合在遍历时已经被修改了。
因此,这里使用了复制、删除、替换的方式来修改manualSingletonNames集合中的元素。先复制原来的集合,然后从副本中删除要删除的元素,最后用副本替换原来的集合,从而保证了在迭代集合的同时修改集合元素的安全性。
这种复制、删除、替换的方式可以确保在迭代集合时不会抛出ConcurrentModificationException异常,但也会增加一定的性能开销。所以,如果集合元素的修改是在迭代之前进行的,可以直接使用remove()方法删除元素,不需要复制、删除、替换的操作。但如果在迭代期间进行了集合元素的修改,就需要采用复制、删除、替换的方式来确保安全性。
我猜是 LinkHashSet 不是线程安全的,如果同时删同一个key=beanName的数据,会有并发修改的问题。复制一份出来,再删不影响其他线程处理,最后重新赋值,哪个线程最后处理以哪个为准。