Java 8性能的经典单例与Lazy
最近我读了一篇文章“懒惰用Java 8”,它介绍了一种创建懒惰对象(在第一次访问时会创建其内部状态的对象)的方法。
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;
}
}
除了泛型外,我发现这种模式与着名的单例模式非常相似:
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();
}
}
}
Lazy类使用volatile成员来同步对内部对象的访问,而singleton模式有很少的实现(我个人更喜欢将它与具有一个静态最终成员的内部帮助器类一起使用)。 我认为第二种模式具有更好的性能,因为每次在Lazy对象上调用getOrCompute方法都会涉及从主内存中读取(由于volatile成员),而Singleton则通过缓存在L1和L2内存缓存中的类加载器加载一次。 我用JMH基准测试了我在CentOS 6上的测试,测试结果是Intel(R)Core(TM)i5-3470 CPU @ 3.20GHz 。 基准可以从我的Git仓库下载:https://github.com/maximkir/LazyObjectVsSingletonPerformance
结果表格如下:
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
结果表明两种方案没有区别,我不明白为什么。 我期望第二种模式会表现更好。 有任何想法吗? 内联? 编译器优化? 错误的基准测试?
基准代码:
@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());
}
我不是并发专家,但看起来你的懒惰初始化器是不正确的。 在基准测试中,您使用Scope.Thread
状态。 但是这意味着每个线程都会拥有它自己的Lazy,所以没有实际的并发性。
我用Lazy(基于apache commons LazyInitializer),Eager和静态内部类写了我自己的基准。
渴望包org.sample;
import java.util.Properties;
public class Eager {
private final Properties value = new Properties();
public Properties get(){
return value;
}
}
惰性包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与你的一样。
基准包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();
}
}
结果
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/6193.html