What are some recommended approaches to achieving thread-safe lazy initialization? For instance,
有哪些推荐的方法可以实现线程安全的延迟初始化?例如,
// Not thread-safe
public Foo getInstance(){
if(INSTANCE == null){
INSTANCE = new Foo();
}
return INSTANCE;
}
47
For singletons there is an elegant solution by delegating the task to the JVM code for static initialization.
对于单例,通过将任务委派给JVM代码进行静态初始化,可以获得一种优雅的解决方案。
public class Something {
private Something() {
}
private static class LazyHolder {
public static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
see
看到
http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
and this blog post of Crazy Bob Lee
以及Crazy Bob Lee的博客文章
http://blog.crazybob.org/2007/01/lazy-loading-singletons.html
http://blog.crazybob.org/2007/01/lazy-loading-singletons.html
38
If you're using Apache Commons Lang, then you can use one of the variations of ConcurrentInitializer like LazyInitializer.
如果您正在使用Apache Commons Lang,那么您可以使用ConcurrentInitializer的一种变体,如LazyInitializer。
Example:
例:
lazyInitializer = new LazyInitializer() {
@Override
protected Foo initialize() throws ConcurrentException {
return new Foo();
}
};
You can now safely get Foo (gets initialized only once):
您现在可以安全地获取Foo(仅初始化一次):
Foo instance = lazyInitializer.get();
If you're using Google's Guava:
如果你使用谷歌的番石榴:
Supplier fooSupplier = Suppliers.memoize(new Supplier() {
public Foo get() {
return new Foo();
}
});
Then call it by Foo f = fooSupplier.get();
然后通过Foo f = fooSupplier.get()调用它;
From Suppliers.memoize javadoc:
来自Suppliers.memoize javadoc:
Returns a supplier which caches the instance retrieved during the first call to get() and returns that value on subsequent calls to get(). The returned supplier is thread-safe. The delegate's get() method will be invoked at most once. If delegate is an instance created by an earlier call to memoize, it is returned directly.
返回一个供应商,它缓存在第一次调用get()期间检索到的实例,并在后续调用get()时返回该值。返回的供应商是线程安全的。委托的get()方法最多只能调用一次。如果delegate是先前调用memoize创建的实例,则直接返回。
24
This can be done in lock-free manner by using AtomicReference
as instance holder:
这可以通过使用AtomicReference作为实例持有者以无锁方式完成:
// in class declaration
private AtomicReference 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;
}
}
Main disadvantage here is that multiple threads can concurrently instantiate two or more Foo
objects, and only one will be lucky to be set up, so if instantiation requires I/O or another shared resource, this method may not be suitable.
这里的主要缺点是多个线程可以同时实例化两个或多个Foo对象,并且只有一个可以设置,因此如果实例化需要I / O或其他共享资源,则此方法可能不合适。
At the other side, this approach is lock-free and wait-free: if one thread which first entered this method is stuck, it won't affect execution of others.
另一方面,这种方法是无锁且无等待的:如果首先进入此方法的一个线程被卡住,它将不会影响其他线程的执行。
9
The easiest way is to use a static inner holder class :
最简单的方法是使用静态内部持有者类:
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
}
2
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
This is called double checking! Check this http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html
这称为双重检查!请查看http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html
1
Thinking about lazy initialization, I would expect getting a "almost real" object that just decorates the still not initialized object.
考虑到延迟初始化,我希望得到一个“几乎真实”的对象,只是装饰仍未初始化的对象。
When the first method is being invoked, the instance within the decorated interface will be initialized.
调用第一个方法时,将初始化装饰界面中的实例。
* Because of the Proxy usage, the initiated object must implement the passed interface.
*由于代理使用,启动的对象必须实现传递的接口。
* The difference from other solutions is the encapsulation of the initiation from the usage. You start working directly with DataSource
as if it was initialized. It will be initialized on the first method's invocation.
*与其他解决方案的不同之处在于从使用中封装了启动。您开始直接使用DataSource,就像它已初始化一样。它将在第一个方法的调用时初始化。
Usage:
用法:
DataSource ds = LazyLoadDecorator.create(dsSupplier, DataSource.class)
Behind the scenes:
幕后制作:
public class LazyLoadDecorator implements InvocationHandler {
private final Object syncLock = new Object();
protected volatile T inner;
private Supplier supplier;
private LazyLoadDecorator(Supplier 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 create(Supplier factory, Class clazz) {
return (T) Proxy.newProxyInstance(LazyLoadDecorator.class.getClassLoader(),
new Class[] {clazz},
new LazyLoadDecorator<>(factory));
}
}
1
Here is one more approach which is based on one-time-executor semantic.
这是另一种基于一次执行器语义的方法。
The full solution with bunch of usage examples can be found on github (https://github.com/ManasjyotiSharma/java_lazy_init). Here is the crux of it:
有大量使用示例的完整解决方案可以在github上找到(https://github.com/ManasjyotiSharma/java_lazy_init)。这是它的关键所在:
“One Time Executor” semantic as the name suggests has below properties:
顾名思义,“一次执行者”语义具有以下属性:
The wrapper provides an execute method which behaves as:
包装器提供了一个执行方法,其行为如下:
The cached output can be safely accessed from outside of the initialization context.
可以从初始化上下文外部安全地访问缓存的输出。
This can be used for initialization as well as non-idempotent de-initialization too.
这也可以用于初始化以及非幂等去初始化。
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 {
private final Function function;
private final AtomicBoolean preGuard;
private final CountDownLatch postGuard;
private final AtomicReference value;
public OneTimeExecutor(Function 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();
}
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();
}
}
Here is a sample usage:
以下是一个示例用法:
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 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 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);
}
}
For few more examples (e.g. singleton initialization which requires some data available only at run-time thus unable to instantiate it in a static block) please refer to the github link mentioned above.
对于更多的例子(例如单例初始化,它需要一些数据仅在运行时可用,因此无法在静态块中实例化),请参考上面提到的github链接。
1
If you use lombok in your project, you can use a feature described here.
如果在项目中使用lombok,则可以使用此处描述的功能。
You just create a field, annotate it with @Getter(lazy=true)
and add initialization, like this: @Getter(lazy=true) private final Foo instance = new Foo();
你只需创建一个字段,用@Getter(lazy = true)注释它并添加初始化,如下所示:@Getter(lazy = true)private final Foo instance = new Foo();
You'll have to reference field only with getter (see notes in lombok docs), but in most cases that's what we need.
你只需要用getter引用字段(参见lombok docs中的注释),但在大多数情况下,这就是我们所需要的。
0
Put the code in a synchronized
block with some suitable lock. There are some other highly specialist techniques, but I'd suggest avoiding those unless absolutely necessary.
将代码放在带有一些合适锁的同步块中。还有其他一些非常专业的技术,但除非绝对必要,否则我建议避免使用这些技术。
Also you've used SHOUTY case, which tends to indicate a static
but an instance method. If it is really static, I suggest you make sure it isn't in any way mutable. If it's just an expensive to create static immutable, then class loading is lazy anyway. You may want to move it to a different (possibly nested) class to delay creation to the absolute last possible moment.
你也使用了SHOUTY case,它倾向于表示静态但是实例方法。如果它是静态的,我建议你确保它没有任何可变性。如果创建静态不可变只是一个昂贵的,那么无论如何类加载都是懒惰的。您可能希望将其移动到另一个(可能是嵌套的)类,以将创建延迟到绝对最后一刻。
0
Depending on what you try to achieve:
取决于您尝试实现的目标:
If you want all Threads to share the same instance, you can make the method synchronized. This will be sufficient
如果希望所有线程共享同一实例,则可以使方法同步。这就足够了
If you want to make a separate INSTANCE for each Thread, you should use java.lang.ThreadLocal
如果要为每个线程创建单独的INSTANCE,则应使用java.lang.ThreadLocal
0
Try to defined the method which gets an instance as synchronized:
尝试定义获取实例同步的方法:
public synchronized Foo getInstance(){
if(INSTANCE == null){
INSTANCE = new Foo();
}
return INSTANCE;
}
Or use a variable:
或者使用变量:
private static final String LOCK = "LOCK";
public synchronized Foo getInstance(){
synchronized(LOCK){
if(INSTANCE == null){
INSTANCE = new Foo();
}
}
return INSTANCE;
}