Spring元码中的神奇操作

img

在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的数据,会有并发修改的问题。复制一份出来,再删不影响其他线程处理,最后重新赋值,哪个线程最后处理以哪个为准。