stream lambda 异常java.lang.IllegalStateException: End size 0 is less than fixed size 20

问题症状

程序中通过遍历map生成简要的summary信息,但是该接口不定时发生异常,绝大部分时候正常,偶尔出现类似如下异常

2022-10-30 21:10:36,171 WARN  [pool-3-thread-1] com.yq.demo.MultiThread: [55] ex when getting summary
java.lang.IllegalStateException: End size 0 is less than fixed size 20
	at java.base/java.util.stream.Nodes$FixedNodeBuilder.end(Nodes.java:1232)
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
	at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
	at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
	at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)
	at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)
	at com.yq.demo.MultiThread.getSummary(MultiThread.java:93)
	at com.yq.demo.MultiThread.lambda$main$1(MultiThread.java:52)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

分析

在网上搜以及分析对应源码后发现, map在stream遍历的时候, 其中entry正好发生了update导致toList出现异常。

参看https://stackoverflow.com/questions/57274369/java-8-streams-java-lang-illegalstateexception-end-size-84758-is-less-than-fix

经过简化的可以复现问题的代码(出问题的和解决问题的都包含在其中)

package com.yq.demo;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.yq.demo.NewResult;
import lombok.extern.slf4j.Slf4j;

import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.*;


@Slf4j
public class MultiThread
{
  private Map<String, String> tableMap = new ConcurrentHashMap<String, String>();
  int count = 1000;
  LoadingCache<String, String> tables;

  public static void main(String[] args) {
    int maxLoop = 90000;

    MultiThread demo = new MultiThread();
    demo.initMap();

    ExecutorService executorService1 = Executors.newFixedThreadPool(3);
    ExecutorService executorService2 = Executors.newFixedThreadPool(3);

    CompletableFuture<NewResult> future1 = CompletableFuture.supplyAsync(() -> {
      log.info("supplyAsync2");
      NewResult result = new NewResult(01, "name01");
      for(int i=0; i < maxLoop; i++) {
        try {
          Thread.sleep(50);
          demo.delRandom();
        } catch(Exception ex) {
          log.warn("ex deleting key", ex);
        }
      }


      return result;
    }, executorService1);

    CompletableFuture<NewResult> future2 = CompletableFuture.supplyAsync(() -> {
      log.info("supplyAsync2");
      NewResult result = new NewResult(02, "name02");
      for(int i=0; i < maxLoop; i++) {
        try {
          Thread.sleep(50);
          List<String> stringList = demo.getSummary();
          log.info("stringList:{}", stringList);
        } catch(Exception ex) {
          log.warn("ex when getting summary", ex);
        }
      }


      return result;
    }, executorService2);

    CompletableFuture<NewResult> future3 = CompletableFuture.supplyAsync(() -> {
      log.info("supplyAsync2");
      NewResult result = new NewResult(03, "name03");
      for(int i=0; i < maxLoop; i++) {
        try {
          Thread.sleep(50);
          List<String> stringList = demo.getSummaryCopy();
          log.info("size:{}, getSummaryCopy:{}", stringList.size(), stringList);
        } catch(Exception ex) {
          log.warn("ex when getting getSummaryCopy", ex);
        }
      }


      return result;
    }, executorService2);


    try {
      future2.get();
      future1.get();
    } catch(Exception ex) {
      log.warn("ex when getting future", ex);
    }

    log.info("done");
  }


  public List<String> getSummary() {
    return tableMap.values().stream().map(String::toLowerCase).toList();
  }

  public List<String> getSummaryCopy() {
    Map<String, String> copyMap = new HashMap<>(tableMap);
//    copy.values().StreamArray().toArray(n -> new VisitDataBE[n]);
    return copyMap.values().stream().map(String::toLowerCase).toList();
  }

  public List<String> getSummaryCopyUnmodifiable() {
    //https://stackoverflow.com/questions/57274369/java-8-streams-java-lang-illegalstateexception-end-size-84758-is-less-than-fix
    //Map<...> copy = new HashMap<>(visits.getVisitDataMap());
    //copy.values().stream().toArray(n -> new VisitDataBE[n]);

    Map<String, String> copyMap = Collections.unmodifiableMap(tableMap);
//    copy.values().StreamArray().toArray(n -> new VisitDataBE[n]);
    return copyMap.values().stream().map(String::toLowerCase).toList();
  }

  public List<String> getSummaryArray() {
    return Arrays.stream(tableMap.values().stream().map(String::toLowerCase).toArray(String[]::new)).toList();
  }


  public void initMap()
  {
    try {
      // 这里为了快速复现问题,所以设置CacheBuilder中3秒就过期
      tables = CacheBuilder.newBuilder()
        .expireAfterAccess(3, TimeUnit.SECONDS)
        .maximumSize(20)
        .build(cacheLoader());

      tableMap = tables.asMap();

      for(int i=0; i< count; i++) {
        //tableMap.put(i+"", "A" + i);
        tables.get(i+"");
      }
    } catch (Exception ex) {
      log.error("ex when getting tables", ex);
    }

    log.info("map size:{}", tableMap.size());
  }

  private CacheLoader<String, String> cacheLoader()
  {
    return new CacheLoader<>()
    {
      @Override
      public String load(String key)
      {
        return "A" + key;
      }
    };
  }


  private void delRandom() {
    SecureRandom secureRandom = new SecureRandom();
    int randomInt = secureRandom.nextInt(count);
    String key = randomInt + "";

    if (tableMap.containsKey(key)) {
      tableMap.remove(key);
      log.info("rm key {}", key);
    }
  }

}

NewResult 类是之前CompletableFuture中用到的,就继续使用,复现问题可以不需要该类

package com.yq.demo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Value;
import lombok.experimental.Accessors;


@Value
@AllArgsConstructor
@Accessors(fluent = true)
public class NewResult {
  int id;
  String name;
}

解决办法

  public List<String> getSummaryCopy() {
     // 在遍历该map前,进行copy式创建一个新的map,这样当我们遍历的时候,原始map中entry发生变化也不会影响我们,代价就是多了一个对象,不过好在这个内容不多,并且一次使用就不使用了,jvm会自动回收。
    Map<String, String> copyMap = new HashMap<>(tableMap);
    return copyMap.values().stream().map(String::toLowerCase).toList();
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值