如何实现线程安全的延迟初始化?
什么是一些推荐的方法来实现线程安全的惰性初始化? 例如,
// Not thread-safe public Foo getInstance(){ if(INSTANCE == null){ INSTANCE = new Foo(); } return INSTANCE; }
对于单身人士来说,通过将任务委派给JVM代码进行静态初始化是一个很好的解决scheme。
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
如果您使用的是Apache Commons Lang ,那么您可以使用ConcurrentInitializer之类的变体之一,如LazyInitializer 。
例:
lazyInitializer = new LazyInitializer<Foo>() { @Override protected Foo initialize() throws ConcurrentException { return new Foo(); } };
你现在可以安全地获得Foo(只进行一次初始化):
Foo instance = lazyInitializer.get();
如果你使用谷歌的番石榴 :
Supplier<Foo> fooSupplier = Suppliers.memoize(new Supplier<Foo>() { public Foo get() { return new Foo(); } });
然后通过Foo f = fooSupplier.get();
调用它Foo f = fooSupplier.get();
来自Suppliers.memoize javadoc :
返回一个供应商,它将在第一次调用get()时检索到的实例caching起来,并在随后调用get()时返回该值。 返回的供应商是线程安全的 。 委托的get()方法最多只能被调用一次 。 如果委托是由先前的memoize调用创build的实例,则直接返回。
这可以通过使用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; } }
这里主要的缺点是multithreading可以同时实例化两个或两个以上的Foo
对象,只有一个会幸运地build立起来,所以如果实例化需要I / O或其他共享资源,这种方法可能不适用。
另一方面,这种方法是无锁和无 等待的 :如果一个线程刚进入这个方法就卡住了,不会影响到其他线程的执行。
最简单的方法是使用一个静态内部持有人类:
public class Singleton { private Singleton() { } public static Singleton getInstance() { return Holder.INSTANCE; } private static class Holder { private static final Singleton INSTANCE = new Singleton(); } }
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
这里有一个基于一次执行者语义的方法。
可以在github( https://github.com/ManasjyotiSharma/java_lazy_init )上find完整的解决scheme。 关键在于:
顾名思义,“一次执行者”的语义具有以下属性:
- 包装函数的包装器对象F.在当前上下文中,F是一个函数/ lambdaexpression式,它包含初始化/取消初始化代码。
-
包装提供了一个执行方法,其行为如下:
- 在第一次调用execute时调用函数F并cachingF的输出。
- 如果有2个或更multithreading同时执行,则只有一个“进入”,其他进程阻塞,直到“进入”完成。
- 对于执行的所有其他/将来的调用,它不会调用F,而只是返回以前caching的输出。
-
caching的输出可以从初始化上下文之外安全地访问。
这可以用于初始化以及非幂等的解除初始化。
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链接。
把代码放在一个synchronized
块中,并使用合适的锁。 还有一些其他高度专业的技术,但我build议避免这些,除非绝对必要。
你也使用了SHOUTY的情况,这往往表明一个static
但实例方法。 如果它真的是静态的,我build议你确保它不是可变的。 如果创build静态不可变的代价太昂贵,那么类加载是懒惰的。 您可能希望将其移动到不同的(可能是嵌套的)类,以将创build延迟到绝对最后的时刻。
取决于你试图达到的目标:
如果您希望所有线程共享相同的实例,则可以使该方法同步。 这将是足够的
如果你想为每个Thread创build一个独立的INSTANCE,你应该使用java.lang.ThreadLocal
尝试定义获取同步实例的方法:
public synchronized Foo getInstance(){ if(INSTANCE == null){ INSTANCE = new Foo(); } return INSTANCE; }
或者使用一个variables:
private static final String LOCK = "LOCK"; public synchronized Foo getInstance(){ synchronized(LOCK){ if(INSTANCE == null){ INSTANCE = new Foo(); } } return INSTANCE; }
考虑懒惰初始化,我期望得到一个“几乎真正”的对象,只是装饰尚未初始化的对象。
当第一个方法被调用时,装饰界面中的实例将被初始化。
*由于代理的使用,启动的对象必须实现传递的接口。
*与其他解决scheme的不同之处在于封装从使用开始。 您开始直接使用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)); } }
如果您在项目中使用lombok,则可以使用此处描述的function。
你只需要创build一个字段,用@Getter(lazy=true)
对它进行注释并添加初始化,就像这样: @Getter(lazy=true) private final Foo instance = new Foo();
您必须仅使用getter引用字段(请参阅lombok 文档中的注释),但在大多数情况下,这就是我们所需要的。