【问题标题】:How to handle state variables in Java8 parallel streams?如何处理 Java8 并行流中的状态变量?
【发布时间】:2023-03-07 18:33:01
【问题描述】:

我们正在使用 Java8 流中的操作链来处理事件流。作为处理的一部分,我们希望跟踪事件计数及其状态以进行测试和监控。这是我们的用例的简化示例,它为给定的日期流打印星期几。

public class StreamStateHandling {

   private static enum Status {RECEIVED, SUCCESS, ERROR};

   private Map<Status,Integer> results = new EnumMap<>(Status.class);

   private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");


   private static Optional<LocalDate> parseDate(String dateString){
    LocalDate localDate = null;
    try {
        localDate = LocalDate.from(formatter.parse(dateString));
    }catch (DateTimeParseException e){
        return Optional.empty();
    }
    return Optional.of(localDate);
   }

   private void doWork(){
    Stream.of("12/31/2014",
            "01-01-2015",
            "12/31/2015",
            "not a date",
            "01/01/2016")
            //.parallel()
            .peek(v -> addResult(Status.RECEIVED))
            .map(StreamStateHandling::parseDate)
            .peek(v -> {if (!v.isPresent()) addResult(Status.ERROR);})
            .filter(Optional::isPresent) 
            .map(Optional::get)
            .map(DayOfWeek::from) 
            .peek(v -> addResult(Status.SUCCESS))
            .forEach(System.out::println); 

       System.out.println(results);
  }
  public static void main(String args[]) {
    new StreamStateHandling().doWork();
  }

  private void addResult(Status status){

    int current = results.getOrDefault(status, 0);
    results.put(status, current + 1);
  }
}

基本上,我们在地图中跟踪状态计数。这在单线程处理中运行良好,但在并行流中会产生不确定的输出。

在现实世界中,我们有多种状态和操作链。一般来说,检测流和跟踪进度的最佳方法是什么?更喜欢 vanilla Java8 实现,但如果使用开源库更容易做到这一点,则可以。

非常感谢您的帮助。

【问题讨论】:

  • .peek.foreach 似乎是多余的。您可以将forEach 放入一个同时满足这两个角色的函数中。

标签: java java-8 reactive-programming


【解决方案1】:

EnumMap 不是线程安全的,addResult() 中的读-修改-写逻辑也不是。尝试使用原子ConcurrentHashMap.merge() 来增加计数:

private Map<Status, Integer> results = new ConcurrentHashMap<>();

private void addResult(Status status) {
    results.merge(status, 1, Integer::sum);
}

【讨论】:

  • 不错!我不知道merge() 功能。在实际实现中,我必须通过二维跟踪计数,并且我使用的 google guava 表尚不支持同步实现。一般来说,这是一个好方法吗?我们正在尝试通过使用并发处理来加快速度,但同步到跟踪状态可能会使其变慢。
  • @Raja 我能想到几个选项。 a) 将ConcurrentHashMap 与复合键一起使用,而不是Table。 b) 使用AtomicIntegers 预先填充您的表,它可以安全地递增而无需锁定。 c) 围绕addResult() 同步。正如您所提到的,根据流的其余部分中发生的处理量,这可能是一个严重的并行化瓶颈。
  • 更一般地说,我认为您的场景是否是一般流的良好用例是值得怀疑的。 Stream 通常应该具有有限的副作用并产生单个聚合结果。您正在尝试从单个流中生成多个结果值。
猜你喜欢
  • 1970-01-01
  • 2015-05-26
  • 1970-01-01
  • 2016-09-11
  • 2021-09-29
  • 1970-01-01
  • 1970-01-01
  • 2020-08-07
  • 1970-01-01
相关资源
最近更新 更多