Skip to content

[BUG]关于SpringValueRegistry.scanAndClean方法报并发修改异常的疑问 #5296

@lzbjut

Description

@lzbjut
  • I have checked the discussions
  • I have searched the issues of this repository and believe that this is not a duplicate.
  • I have checked the FAQ of this repository and believe that this is not a duplicate.

Describe the bug
最近使用旧版apollo时遇到prototype-bean导致内存被打满的问题,查找issue找到#1670
在查看相关方法时,找到了遇到并发问题并解决的:#2281
但是查看SpringValueRegistry类的issue还是发现了:#4355#4356#4800#4925#5169
问题主要集中于scanAndClean还是有报并发错误,我查看synchronizedListMultimap方法的注释时,发现有相关叙述:

  /**
   * Returns a synchronized (thread-safe) multimap backed by the specified multimap. In order to
   * guarantee serial access, it is critical that <b>all</b> access to the backing multimap is
   * accomplished through the returned multimap.
   *
   * <p>It is imperative that the user manually synchronize on the returned multimap when accessing
   * any of its collection views:
   *
   * <pre>{@code
   * Multimap<K, V> multimap = Multimaps.synchronizedMultimap(
   *     HashMultimap.<K, V>create());
   * ...
   * Collection<V> values = multimap.get(key);  // Needn't be in synchronized block
   * ...
   * synchronized (multimap) {  // Synchronizing on multimap, not values!
   *   Iterator<V> i = values.iterator(); // Must be in synchronized block
   *   while (i.hasNext()) {
   *     foo(i.next());
   *   }
   * }
   * }</pre>
   *
   * <p>Failure to follow this advice may result in non-deterministic behavior.
   *
   * <p>Note that the generated multimap's {@link Multimap#removeAll} and {@link
   * Multimap#replaceValues} methods return collections that aren't synchronized.
   *
   * <p>The returned multimap will be serializable if the specified multimap is serializable.
   *
   * @param multimap the multimap to be wrapped in a synchronized view
   * @return a synchronized view of the specified multimap
   */

其中It is imperative that the user manually synchronize on the returned multimap when accessing any of its collection views:
Failure to follow this advice may result in non-deterministic behavior.
是否说明synchronizedMultimap的iterator实际上是非线程安全的,于是我就尝试自己写了一个简单的复现代码,并获得了其他issue中提到的错误
To Reproduce
复现这个synchronizedMultimap并发写入并定期清理导致报错的场景,我复现出其中一种,即输入固定量的k-v,定时清除,在写入停止后,因为剩余值(registry.entries().size())为负,在获取iterator时会报错:negative size

    @Test
    public void testConfig() {
        Multimap<String, String> registry = Multimaps.synchronizedListMultimap(LinkedListMultimap.create());
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        AtomicInteger removeTotal = new AtomicInteger();
        for(int i=0;i<10;i++){
            executorService.submit(()->{
                try {
                    for(int j = 0;j<100000;j++){
                        Thread.sleep(1);
                        registry.put("aa",System.currentTimeMillis()+"");
                    }
                    System.out.println("done");

                }catch (Exception e){
                    e.printStackTrace();
                }

            });
        }
        scheduledExecutorService.scheduleWithFixedDelay(()->{
                try {
                    long start = System.currentTimeMillis();
                        Iterator<Map.Entry<String, String>> ss = registry.entries().iterator();
                        while (ss.hasNext()) {
                                Map.Entry<String, String> entry = ss.next();
                                if (!StringUtils.isNullOrEmpty(entry.getValue())) {
                                    ss.remove();
                                    removeTotal.incrementAndGet();
                                }
                        }
                    System.out.println("cost:"+(System.currentTimeMillis()-start)+"ms");
                    System.out.println("removed total: "+removeTotal.get());
                    System.out.println("remain: "+registry.entries().size());

                }catch (Exception e){
                    System.out.println(e.getMessage());
                    e.printStackTrace();
                }

            },5,5,TimeUnit.SECONDS);
        try {
            Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Additional Details & Logs

当我为清理代码增加synchronized后该问题解决:

    synchronized (registry) {
        Iterator<Map.Entry<String, String>> ss = registry.entries().iterator();
        while (ss.hasNext()) {
            Map.Entry<String, String> entry = ss.next();
            if (!StringUtils.isNullOrEmpty(entry.getValue())) {
                ss.remove();
                removeTotal.incrementAndGet();
            }
        }
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions