Classic singleton vs. Lazy with Java 8 Performance

Recently I read an article "Be Lazy With Java 8", which introduced a way to create a lazy objects (objects that will created their internal state on the first access).

public final class Lazy<T> {

    private volatile T value;

    public T getOrCompute(Supplier<T> supplier){
        final T result = value;
        return result == null ? maybeCompute(supplier) : result;
    }

    private synchronized T maybeCompute(Supplier<T> supplier) {
        if (value == null){
            value = Objects.requireNonNull(supplier.get());
        }
        return value;
    }
}

I found this pattern to be very similar to the well-known singleton pattern, except the generics:

public class PropertiesSingleton {

    public static Properties getProperties(){
        return Helper.INSTANCE;
    }

    private final static class Helper{
        private final static Properties INSTANCE = computeWithClassLoaderLock();


        private static Properties computeWithClassLoaderLock(){
            return new Properties();
        }
    }
}

The Lazy class uses volatile member to synchronize access to the internal object while the singleton pattern has few implementations (I personally prefer to use it with inner helper class that have one static final member). I assumed that the second pattern have better performance since each call to getOrCompute method on Lazy object involve a read from the main memory (due to volatile member) while the Singleton loaded once by the class loader cached in L1 & L2 memory caches. I used JMH benchmark to test my assumption on CentOS 6 with Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz . The benchmark can be downloaded from my Git repository: https://github.com/maximkir/LazyObjectVsSingletonPerformance

Here are the results table:

Benchmark                                   Mode      Cnt   Score   Error  Units
LazyVsSingletonPerformance.testLazy       sample  1101716  33.793 ± 0.148  ns/op
LazyVsSingletonPerformance.testSingleton  sample   622603  33.993 ± 0.179  ns/op

The results show that no difference between the two options, I am not understanding why. I would expect that the second pattern will perform better. Any ideas? inlining? Compiler optimization? Wrong benchmark test?

The Benchmark code:

@State(Scope.Thread)
public class LazyVsSingletonPerformance {

    Blackhole bh = new Blackhole();
    Lazy<Properties> lazyProperties = new Lazy<>();

    public static void main(String... args) throws Exception{
        Options opts = new OptionsBuilder()
                .include(LazyVsSingletonPerformance.class.getSimpleName())
                .warmupIterations(3)
                .forks(2)
                .measurementIterations(3)
                .mode(Mode.SampleTime)
                .measurementTime(TimeValue.seconds(10))
                .timeUnit(TimeUnit.NANOSECONDS)
                .build();

        new Runner(opts).run();
    }


    @Benchmark
    public void testLazy(){
        bh.consume(lazyProperties.getOrCompute(() -> new Properties()));
    }


    @Benchmark
    public void testSingleton(){
        bh.consume(PropertiesSingleton.getProperties());
    }

I'm not an expert in concurrency, but it seems that your Lazy initializer is incorrect. In benchmark you use Scope.Thread state. But that means that each thread will have it's own Lazy, so there is no real concurrency.

I wrote my own benchmark with Lazy (based on apache commons LazyInitializer), Eager and static inner class.

Eager package org.sample;

import java.util.Properties;

public class Eager {
    private final Properties value = new Properties();

    public Properties get(){
        return value;
    }
}

Lazy package org.sample;

import org.apache.commons.lang3.concurrent.ConcurrentException;
import org.apache.commons.lang3.concurrent.LazyInitializer;

import java.util.Properties;

public class Lazy extends LazyInitializer<Properties> {
    @Override
    protected Properties initialize() throws ConcurrentException {
        return new Properties();
    }
}

PropertiesSingleton is as yours.

Benchmark package org.sample;

import org.apache.commons.lang3.concurrent.ConcurrentException;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;

import java.util.Properties;

@State(Scope.Benchmark)
public class MyBenchmark {
    private Lazy lazyProperties = new Lazy();
    private Eager eagerProperties = new Eager();

    @Benchmark
    public Properties testEager(){
        return eagerProperties.get();
    }

    @Benchmark
    public Properties testLazy() throws ConcurrentException {
        return lazyProperties.get();
    }    

    @Benchmark
    public Properties testSingleton(){
        return PropertiesSingleton.getProperties();
    }
}

Results

Benchmark                   Mode  Cnt         Score         Error  Units
MyBenchmark.testEager      thrpt   20  90980753,160 ± 4075331,777  ops/s
MyBenchmark.testLazy       thrpt   20  83876826,598 ± 3445507,139  ops/s
MyBenchmark.testSingleton  thrpt   20  82260350,608 ± 3524764,266  ops/s
链接地址: http://www.djcxy.com/p/6194.html

上一篇: 语句和按位操作在这个例子中是相同的?

下一篇: Java 8性能的经典单例与Lazy