【问题标题】:Does Java 8 have cached support for suppliers?Java 8 是否对供应商提供缓存支持?
【发布时间】:2016-02-11 05:18:53
【问题描述】:

guava 库有它自己的 Supplier,它不扩展 Java 8 Supplier。 guava 还为供应商提供了缓存 - Suppliers#memoize。

有没有类似的东西,但对于 Java 8 供应商?

【问题讨论】:

  • 不完全是,但您可以在 j.u.f.Suppliers 和 c.g.c.b.Suppliers 之间轻松转换,只需在末尾写 ::get。
  • 正如@LouisWasserman 建议的那样,您可以通过基本上执行“return Suppliers.memoize(delegate::get)::get;”为番石榴 Suppliers::memoize 制作一个包装器
  • Suppliers.memoize 没有进入 jdk8 标准库绝对是一个遗憾,考虑到它对我来说似乎是一个非常低风险的补充。

标签: java java-8 guava


【解决方案1】:

没有用于记忆的内置 Java 函数,虽然实现起来并不难,例如,像这样:

public static <T> Supplier<T> memoize(Supplier<T> delegate) {
    AtomicReference<T> value = new AtomicReference<>();
    return () -> {
        T val = value.get();
        if (val == null) {
            val = value.updateAndGet(cur -> cur == null ? 
                    Objects.requireNonNull(delegate.get()) : cur);
        }
        return val;
    };
}

请注意,存在不同的实现方法。如果被记忆的供应商同时从不同的线程多次请求,上述实现可能会多次调用委托。有时,这种实现比使用锁的显式同步更可取。如果首选锁定,则可以使用 DCL:

public static <T> Supplier<T> memoizeLock(Supplier<T> delegate) {
    AtomicReference<T> value = new AtomicReference<>();
    return () -> {
        T val = value.get();
        if (val == null) {
            synchronized(value) {
                val = value.get();
                if (val == null) {
                    val = Objects.requireNonNull(delegate.get());
                    value.set(val);
                }
            }
        }
        return val;
    };
}

还请注意,正如 @LouisWasserman 在 cmets 中正确提到的那样,您可以使用方法参考轻松地将 JDK 供应商转换为 Guava 供应商,反之亦然:

java.util.function.Supplier<String> jdkSupplier = () -> "test";
com.google.common.base.Supplier<String> guavaSupplier = jdkSupplier::get;
java.util.function.Supplier<String> jdkSupplierBack = guavaSupplier::get;

所以在 Guava 和 JDK 功能之间切换也不是什么大问题。

【讨论】:

  • 在这种情况下你真的不需要AtomicReference,对吗?它似乎只是用作 lambda 可以关闭的可变容器。如果您想保存一个对象分配,我认为您可以返回一个带有易失性value 字段的匿名类实例。 this 上的同步。
  • @Lii, AtomicReference 只是一个具有单个字段的对象,它提供了这里必要的易失读/写语义。可以用匿名类 volatile 字段替换它(仅在第二个示例中,不在第一个示例中),但这种优化是否重要并不是很明显。此外,锁定公开可用的对象被认为是一种不好的做法。
  • 您可以通过记住()-&gt;val 形式的另一个提供者来消除易失语义。这样,您将使用捕获值的 final 字段语义。
【解决方案2】:

最简单的解决方案是

public static <T> Supplier<T> memoize(Supplier<T> original) {
    ConcurrentHashMap<Object, T> store=new ConcurrentHashMap<>();
    return ()->store.computeIfAbsent("dummy", key->original.get());
}

然而,最简单的并不总是最有效的。

如果你想要一个干净高效的解决方案,诉诸匿名内部类来保持可变状态会得到回报:

public static <T> Supplier<T> memoize1(Supplier<T> original) {
    return new Supplier<T>() {
        Supplier<T> delegate = this::firstTime;
        boolean initialized;
        public T get() {
            return delegate.get();
        }
        private synchronized T firstTime() {
            if(!initialized) {
                T value=original.get();
                delegate=() -> value;
                initialized=true;
            }
            return delegate.get();
        }
    };
}

这使用了一个委托供应商,该供应商将执行第一次操作,然后将自己替换为无条件返回第一次评估的捕获结果的供应商。由于它具有final 字段语义,因此无需任何额外同步即可无条件返回。

在 synchronized 方法 firstTime() 内部,仍然需要一个 initialized 标志,因为在初始化期间并发访问的情况下,多个线程可能会在委托被替换之前在方法的入口处等待。因此,这些线程需要检测初始化是否已经完成。所有后续访问都会读取新的委托供应商并快速获取值。

【讨论】:

  • @glts: delegate.get() 将结束于synchronized 方法firstTime() 用于第一次调用或与() -&gt; value lambda 表达式关联的实例,而@ 987654331@ 实际上是最终的。访问该捕获值相当于读取 final 字段,无需额外同步即可安全。如果线程看到delegate 引用的陈旧值,它将通过synchronized firstTime() 方法进行一次调用,之后将知道最新值,因此所有后续调用都会快速进行那么路径。
  • 为什么delegate在这种情况下不需要标记volatile?
  • @Mark Elliot:捕获的局部变量值具有final 字段语义,因此如果线程遇到新委托,即() -&gt; value,它也会正确读取value。由于没有volatile,一个线程可能会遇到旧的delegate,但是在这种情况下它会进入synchronized方法firstTime()并读取initialized和@987654346的最新值@。对于每个线程,这可能最多发生一次,因为之后,它具有最新的值。
  • @CodeConfident:答案的最后一段对此进行了解释。如果多个线程在处于未初始化状态时同时调用get(),它们都会读取对初始供应商的引用并尝试进入firstTime()方法,但由于synchronized而被阻塞的只有一个。在第一个线程完成初始化后,待处理的线程将一个接一个地继续,每个线程都必须检测到初始化已经完成。这是一种罕见的情况,但在一般解决方案中,必须处理。
  • 对单个元素使用并发 hashmap 来获取锁对我来说似乎是一种巨大的浪费。
【解决方案3】:

Java 8 上 Guava 20 的简单包装器:

static <T> java.util.function.Supplier<T> memoize(java.util.function.Supplier<? extends T> supplier) {
    return com.google.common.base.Suppliers.memoize(supplier::get)::get;
}

【讨论】:

  • 我想你可以直接返回com.google.common.base.Suppliers.memoize(supplier::get)。 Guava 供应商扩展了 java 供应商。
  • @PawelZieminski 对,如果是 Guava 21 或更高版本会更好github.com/google/guava/wiki/Release21#commonbase
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多