如何实现线程安全的延迟初始化?

时间:2011-11-28 14:57:33

标签: java thread-safety lazy-initialization

实现 线程安全 延迟初始化的一些推荐方法是什么?例如,

// Not thread-safe
public Foo getInstance(){
    if(INSTANCE == null){
        INSTANCE = new Foo();
    }

    return INSTANCE;
}

12 个答案:

答案 0 :(得分:52)

对于单身人士来说,通过将任务委托给JVM代码进行静态初始化,可以得到一个优雅的解决方案。

public class Something {
    private Something() {
    }

    private static class LazyHolder {
            public static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
            return LazyHolder.INSTANCE;
    }
}

http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

和Crazy Bob Lee的博客文章

http://blog.crazybob.org/2007/01/lazy-loading-singletons.html

答案 1 :(得分:45)

如果您使用 Apache Commons Lang ,则可以使用ConcurrentInitializer之类的LazyInitializer变体之一。

示例:

lazyInitializer = new LazyInitializer<Foo>() {

        @Override
        protected Foo initialize() throws ConcurrentException {
            return new Foo();
        }
    };

您现在可以安全地获取Foo(仅初始化一次):

Foo instance = lazyInitializer.get();

如果您使用的是 Google的番石榴

Supplier<Foo> fooSupplier = Suppliers.memoize(new Supplier<Foo>() {
    public Foo get() {
        return new Foo();
    }
});

然后通过Foo f = fooSupplier.get();

进行调用

来自Suppliers.memoize javadoc

  

返回一个供应商,该供应商缓存在第一次调用get()期间检索到的实例,并在后续调用get()时返回该值。返回的供应商线程安全。委托的get()方法最多只能调用 。如果delegate是先前调用memoize创建的实例,则直接返回。

答案 2 :(得分:28)

这可以通过使用AtomicReference作为实例持有者以无锁方式完成:

// in class declaration
private AtomicReference<Foo> instance = new AtomicReference<>(null);  

public Foo getInstance() {
   Foo foo = instance.get();
   if (foo == null) {
       foo = new Foo();                       // create and initialize actual instance
       if (instance.compareAndSet(null, foo)) // CAS succeeded
           return foo;
       else                                   // CAS failed: other thread set an object 
           return instance.get();             
   } else {
       return foo;
   }
}

这里的主要缺点是多个线程可以同时实例化两个或多个Foo对象,并且只有一个可以设置,所以如果实例化需要I / O或其他共享资源,这个方法可能不会适合。

另一方面,此方法 无锁 无等待 :如果一个主题首次进入此方法的方法被卡住了,不会影响其他人的执行。

答案 3 :(得分:9)

最简单的方法是使用静态内部持有者类:

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

答案 4 :(得分:3)

class Foo {
  private volatile Helper helper = null;
  public Helper getHelper() {
    if (helper == null) {
      synchronized(this) {
        if (helper == null) {
          helper = new Helper();
        }
      }
    }
  return helper;
}

这叫做双重检查! 请检查此http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html

答案 5 :(得分:3)

如果您在项目中使用lombok,则可以使用描述为here的功能。

您只需创建一个字段,使用@Getter(lazy=true)对其进行注释并添加初始化,如下所示: @Getter(lazy=true) private final Foo instance = new Foo();

您必须仅使用getter引用字段(请参阅lombok docs中的注释),但在大多数情况下,这就是我们需要的内容。

答案 6 :(得分:1)

考虑到懒惰初始化,我希望得到一个几乎真实的&#34;只装饰尚未初始化的对象的对象。

当调用第一个方法时,将初始化装饰界面中的实例。

*由于代理使用,启动的对象必须实现传递的接口。

*与其他解决方案的不同之处在于从使用中封装了启动。您开始直接使用DataSource,就像它已初始化一样。它将在第一个方法的调用中初始化。

<强>用法:

DataSource ds = LazyLoadDecorator.create(dsSupplier, DataSource.class)

在幕后:

public class LazyLoadDecorator<T> implements InvocationHandler {

    private final Object syncLock = new Object();
    protected volatile T inner;
    private Supplier<T> supplier;

    private LazyLoadDecorator(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (inner == null) {
            synchronized (syncLock) {
                if (inner == null) {
                    inner = load();
                }
            }
        }
        return method.invoke(inner, args);
    }

    protected T load() {
        return supplier.get();
    }

    @SuppressWarnings("unchecked")
    public static <T> T create(Supplier<T> factory, Class<T> clazz) {
        return (T) Proxy.newProxyInstance(LazyLoadDecorator.class.getClassLoader(),
                new Class[] {clazz},
                new LazyLoadDecorator<>(factory));
    }
}

答案 7 :(得分:1)

这是另一种基于一次性执行器语义的方法。

可以在github(https://github.com/ManasjyotiSharma/java_lazy_init)上找到包含大量使用示例的完整解决方案。这是它的关键所在:

“One Time Executor”语义顾名思义具有以下属性:

  1. 包装函数F的包装器对象。在当前上下文中,F是一个函数/ lambda表达式,它保存初始化/去初始化代码。
  2. 包装器提供了一个执行方法,其行为如下:

    • 第一次调用execute时调用函数F并缓存F的输出。
    • 如果两个或多个线程同时调用执行,则只有一个“进入”而其他线程阻塞直到“进入”的线程完成。
    • 对于所有其他/将来的执行调用,它不会调用F而只是简单地返回先前缓存的输出。
  3. 可以从初始化上下文外部安全地访问缓存的输出。

  4. 这也可以用于初始化以及非幂等去初始化。

    import java.util.Objects;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.function.Function;
    
    /**
     * When execute is called, it is guaranteed that the input function will be applied exactly once. 
     * Further it's also guaranteed that execute will return only when the input function was applied
     * by the calling thread or some other thread OR if the calling thread is interrupted.
     */
    
    public class OneTimeExecutor<T, R> {  
      private final Function<T, R> function;
      private final AtomicBoolean preGuard;
      private final CountDownLatch postGuard;
      private final AtomicReference<R> value;
    
      public OneTimeExecutor(Function<T, R> function) {
        Objects.requireNonNull(function, "function cannot be null");
        this.function = function;
        this.preGuard = new AtomicBoolean(false);
        this.postGuard = new CountDownLatch(1);
        this.value = new AtomicReference<R>();
      }
    
      public R execute(T input) throws InterruptedException {
        if (preGuard.compareAndSet(false, true)) {
          try {
            value.set(function.apply(input));
          } finally {
            postGuard.countDown();
          }
        } else if (postGuard.getCount() != 0) {
          postGuard.await();
        }
        return value();
      }
    
      public boolean executed() {
        return (preGuard.get() && postGuard.getCount() == 0);
      }
    
      public R value() {
        return value.get();
      }
    
    }  
    

    以下是一个示例用法:

    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.nio.charset.StandardCharsets;
    
    /*
     * For the sake of this example, assume that creating a PrintWriter is a costly operation and we'd want to lazily initialize it.
     * Further assume that the cleanup/close implementation is non-idempotent. In other words, just like initialization, the 
     * de-initialization should also happen once and only once.
     */
    public class NonSingletonSampleB {
      private final OneTimeExecutor<File, PrintWriter> initializer = new OneTimeExecutor<>(
        (File configFile) -> {
          try { 
            FileOutputStream fos = new FileOutputStream(configFile);
            OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
            BufferedWriter bw = new BufferedWriter(osw);
            PrintWriter pw = new PrintWriter(bw);
            return pw;
          } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
          }
        }
      );  
    
      private final OneTimeExecutor<Void, Void> deinitializer = new OneTimeExecutor<>(
        (Void v) -> {
          if (initializer.executed() && null != initializer.value()) {
            initializer.value().close();
          }
          return null;
        }
      );  
    
      private final File file;
    
      public NonSingletonSampleB(File file) {
        this.file = file;
      }
    
      public void doSomething() throws Exception {
        // Create one-and-only-one instance of PrintWriter only when someone calls doSomething().  
        PrintWriter pw = initializer.execute(file);
    
        // Application logic goes here, say write something to the file using the PrintWriter.
      }
    
      public void close() throws Exception {
        // non-idempotent close, the de-initialization lambda is invoked only once. 
        deinitializer.execute(null);
      }
    
    }
    

    还有更多示例(例如,单例初始化需要一些数据仅在运行时可用,因此无法在静态块中实例化),请参阅上面提到的github链接。

答案 8 :(得分:0)

将代码放在具有适当锁定的synchronized块中。还有其他一些非常专业的技术,但除非绝对必要,否则我建议避免使用这些技术。

此外,您使用了SHOUTY大小写,它往往表示static但是实例方法。如果它真的是静态的,我建议你确保它没有任何可变性。如果创建静态不可变只是一个昂贵的,那么无论如何类加载都是懒惰的。您可能希望将其移动到另一个(可能是嵌套的)类,以将创建延迟到绝对最后一刻。

答案 9 :(得分:0)

取决于您尝试实现的目标:

如果希望所有线程共享同一个实例,可以使方法同步。这就足够了

如果你想为每个Thread创建一个单独的INSTANCE,你应该使用java.lang.ThreadLocal

答案 10 :(得分:0)

使用Java 8,我们可以在线程安全的情况下实现延迟初始化。如果我们拥有Holder类,并且它需要一些繁重的资源,则可以像这样延迟加载繁重的资源。

public class Holder {
    private Supplier<Heavy> heavy = () -> createAndCacheHeavy();

    private synchronized Heavy createAndCacheHeavy() {

        class HeavyFactory implements Supplier<Heavy> {
            private final Heavy heavyInstance = new Heavy();

            @Override
            public Heavy get() {
                return heavyInstance;
            }
        }
        if (!HeavyFactory.class.isInstance(heavy)) {
            heavy = new HeavyFactory();
        }
        return heavy.get();
    }

    public Heavy getHeavy() {
        return heavy.get();
    }
}

public class Heavy {
    public Heavy() {
        System.out.println("creating heavy");
    }
}

答案 11 :(得分:-1)

尝试定义将实例变为同步的方法:

public synchronized Foo getInstance(){
   if(INSTANCE == null){
    INSTANCE = new Foo();
  }

  return INSTANCE;
 }

或使用变量:

private static final String LOCK = "LOCK";
public synchronized Foo getInstance(){
  synchronized(LOCK){
     if(INSTANCE == null){
       INSTANCE = new Foo();
     }
  }
  return INSTANCE;
 }