【问题标题】:Generic memoize function throws ConcurrentModificationException通用 memoize 函数抛出 ConcurrentModificationException
【发布时间】:2020-07-16 08:09:52
【问题描述】:

我这样定义了一个通用的 memoize 函数。

public static <T, U> Function<T, U> memoize(Function<Function<T, U>, Function<T, U>> f) {
    return new Function<T, U>() {

        final Map<T, U> cache = new HashMap<>();
        final Function<T, U> body = f.apply(this);

        @Override
        public U apply(T t) {
            return cache.computeIfAbsent(t, body);
        }
    };
}

它适用于单参数函数。

Function<BigInteger, BigInteger> fibonacci =
    memoize(self -> n -> n.equals(ZERO) ? ZERO
        : n.equals(ONE) ? ONE
        : self.apply(n.subtract(ONE)).add(self.apply(n.subtract(TWO))));

for (long i = 0; i < 1000; ++i)
    System.out.println(fibonacci.apply(BigInteger.valueOf(i)));

但是当我尝试将其应用于 curried 多参数函数时(例如 Tak (function)-Wikipedia 中的 tarai)。

Function<Integer, Function<Integer, Function<Integer, Integer>>> tarai =
    memoize(fx -> x ->
        memoize(fy -> y ->
            memoize(fz -> z -> x <= y ? y
                : fx.apply(fx.apply(x - 1).apply(y).apply(z))
                    .apply(fx.apply(y - 1).apply(z).apply(x))
                    .apply(fx.apply(z - 1).apply(x).apply(y)))));

System.out.println(tarai.apply(12).apply(6).apply(0));

它抛出ConcurrentModificaionException。 为什么会这样?我该怎么办?

我使用的是 Java 14,但我记得它在过去的版本中也可以使用。

【问题讨论】:

  • 添加堆栈跟踪可能有助于回答您的问题。

标签: java memoization


【解决方案1】:

computeIfAbsent 的 Javadoc 说:

如果检测到映射函数在计算期间修改此映射,此方法将尽最大努力抛出 ConcurrentModificationException

大概是因为它在内部迭代地图。

鉴于此,另一种方法是手动进行“如果不存在则计算”:

if (map.containsKey(t)) {
  return map.get(t);
}
U value = body.apply(t);
map.put(t, value);
return value;

【讨论】:

  • 为什么只有嵌套时才会出现?
  • @saka1029 因为在这种情况下,尽力检测修改会检测到修改。是因为简单的情况下没有共修改,还是没有检测到,我不知道,你需要调试它。
【解决方案2】:

computeIfAbsent 不允许递归调用。

您将函数作为第二个参数传递。该函数在同一个对象上调用computeIfAbsent

实施明确禁止:

        int mc = this.modCount;
        v = mappingFunction.apply(key);
        if (mc != this.modCount) {
            throw new ConcurrentModificationException();
        } 

这个检查好像是在Java 1.8之后引入的。

【讨论】:

  • 是不是也递归调用fibonacci
  • 我遇到了同样的斐波那契异常。不过我正在使用java 11
  • fib 不会出现异常,因为它在循环中。它填充地图,允许下一次执行使用上一次的结果。如果你只是打电话给System.out.println(fibonacci.apply(BigInteger.valueOf(5))); 你会有例外。
猜你喜欢
  • 2013-02-12
  • 2013-08-09
  • 1970-01-01
  • 2010-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多