Java 8 性能的经典单例与惰性

Max*_*lov 5 performance java-8 jmh

最近读了一篇文章《Be Lazy With Java 8》 ”,其中介绍了一种创建惰性对象(在第一次访问时创建其内部状态的对象)的方法。

\n\n
public final class Lazy<T> {\n\n    private volatile T value;\n\n    public T getOrCompute(Supplier<T> supplier){\n        final T result = value;\n        return result == null ? maybeCompute(supplier) : result;\n    }\n\n    private synchronized T maybeCompute(Supplier<T> supplier) {\n        if (value == null){\n            value = Objects.requireNonNull(supplier.get());\n        }\n        return value;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我发现这个模式与众所周知的单例模式非常相似,除了泛型之外:

\n\n
public class PropertiesSingleton {\n\n    public static Properties getProperties(){\n        return Helper.INSTANCE;\n    }\n\n    private final static class Helper{\n        private final static Properties INSTANCE = computeWithClassLoaderLock();\n\n\n        private static Properties computeWithClassLoaderLock(){\n            return new Properties();\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

Lazy 类使用易失性成员来同步对内部对象的访问,而单例模式几乎没有实现(我个人更喜欢将其与具有一个静态最终成员的内部帮助器类一起使用)。我认为第二种模式具有更好的性能,因为每次对Lazy 对象上的getOrCompute方法的调用都涉及从主内存中读取(由于易失性成员),而单例由缓存在 L1 和 L2 内存缓存中的类加载器加载一次。\nI使用JMH基准测试在配备 Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz 的 CentOS 6上测试我的假设。该基准测试可以从我的 Git 存储库下载:https://github.com/maximkir/LazyObjectVsSingletonPerformance

\n\n

以下是结果表:

\n\n
Benchmark                                   Mode      Cnt   Score   Error  Units\nLazyVsSingletonPerformance.testLazy       sample  1101716  33.793 \xc2\xb1 0.148  ns/op\nLazyVsSingletonPerformance.testSingleton  sample   622603  33.993 \xc2\xb1 0.179  ns/op\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果显示两个选项没有区别,我不明白为什么。我预计第二种模式会表现得更好。有任何想法吗?内联?编译器优化?基准测试错误?

\n\n

基准代码:

\n\n
@State(Scope.Thread)\npublic class LazyVsSingletonPerformance {\n\n    Blackhole bh = new Blackhole();\n    Lazy<Properties> lazyProperties = new Lazy<>();\n\n    public static void main(String... args) throws Exception{\n        Options opts = new OptionsBuilder()\n                .include(LazyVsSingletonPerformance.class.getSimpleName())\n                .warmupIterations(3)\n                .forks(2)\n                .measurementIterations(3)\n                .mode(Mode.SampleTime)\n                .measurementTime(TimeValue.seconds(10))\n                .timeUnit(TimeUnit.NANOSECONDS)\n                .build();\n\n        new Runner(opts).run();\n    }\n\n\n    @Benchmark\n    public void testLazy(){\n        bh.consume(lazyProperties.getOrCompute(() -> new Properties()));\n    }\n\n\n    @Benchmark\n    public void testSingleton(){\n        bh.consume(PropertiesSingleton.getProperties());\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

Den*_*rin 0

我不是并发方面的专家,但您的惰性初始化程序似乎不正确。在基准测试中,您使用Scope.Thread状态。但这意味着每个线程都有自己的Lazy,因此不存在真正的并发。

\n\n

我用 Lazy (基于 apache commons LazyInitializer)、Eager 和静态内部类编写了自己的基准测试。

\n\n

渴望\n包org.sample;

\n\n
import java.util.Properties;\n\npublic class Eager {\n    private final Properties value = new Properties();\n\n    public Properties get(){\n        return value;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

懒惰\n包org.sample;

\n\n
import org.apache.commons.lang3.concurrent.ConcurrentException;\nimport org.apache.commons.lang3.concurrent.LazyInitializer;\n\nimport java.util.Properties;\n\npublic class Lazy extends LazyInitializer<Properties> {\n    @Override\n    protected Properties initialize() throws ConcurrentException {\n        return new Properties();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

PropertiesSingleton 是你的。

\n\n

基准测试\n 包 org.sample;

\n\n
import org.apache.commons.lang3.concurrent.ConcurrentException;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.util.Properties;\n\n@State(Scope.Benchmark)\npublic class MyBenchmark {\n    private Lazy lazyProperties = new Lazy();\n    private Eager eagerProperties = new Eager();\n\n    @Benchmark\n    public Properties testEager(){\n        return eagerProperties.get();\n    }\n\n    @Benchmark\n    public Properties testLazy() throws ConcurrentException {\n        return lazyProperties.get();\n    }    \n\n    @Benchmark\n    public Properties testSingleton(){\n        return PropertiesSingleton.getProperties();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果

\n\n
Benchmark                   Mode  Cnt         Score         Error  Units\nMyBenchmark.testEager      thrpt   20  90980753,160 \xc2\xb1 4075331,777  ops/s\nMyBenchmark.testLazy       thrpt   20  83876826,598 \xc2\xb1 3445507,139  ops/s\nMyBenchmark.testSingleton  thrpt   20  82260350,608 \xc2\xb1 3524764,266  ops/s\n
Run Code Online (Sandbox Code Playgroud)\n