有哪些推荐的方法来实现线程安全的延迟初始化?例如,
// Not thread-safe
public Foo getInstance(){
if(INSTANCE == null){
INSTANCE = new Foo();
}
return INSTANCE;
}
有哪些推荐的方法来实现线程安全的延迟初始化?例如,
// Not thread-safe
public Foo getInstance(){
if(INSTANCE == null){
INSTANCE = new Foo();
}
return INSTANCE;
}
如果您使用的是Apache Commons Lang,那么您可以使用ConcurrentInitializer的一种变体,例如LazyInitializer。
例子:
ConcurrentInitializer<Foo> lazyInitializer = new LazyInitializer<Foo>() {
@Override
protected Foo initialize() throws ConcurrentException {
return new Foo();
}
};
您现在可以安全地获取 Foo(仅初始化一次):
Foo instance = lazyInitializer.get();
如果您使用的是Google 的 Guava:
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 创建的实例,则直接返回。
对于单例,有一个优雅的解决方案,将任务委托给 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
和疯狂鲍勃·李的这篇博文
http://blog.crazybob.org/2007/01/lazy-loading-singletons.html
这可以通过使用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 或其他共享资源,这种方法可能不适合。
另一方面,这种方法是无锁和无等待的:如果第一个进入该方法的线程被卡住,不会影响其他线程的执行。
最简单的方法是使用静态内部持有者类:
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
考虑延迟初始化,我希望得到一个“几乎真实”的对象,它只是装饰仍未初始化的对象。
当第一个方法被调用时,装饰接口中的实例将被初始化。
* 因为使用了Proxy,所以发起的对象必须实现传递的接口。
* 与其他解决方案的不同之处在于从使用上对启动的封装。您开始直接使用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));
}
}
这是另一种基于一次性执行器语义的方法。
可以在 github ( https://github.com/ManasjyotiSharma/java_lazy_init ) 上找到包含大量使用示例的完整解决方案。这是它的症结所在:
顾名思义,“One Time Executor”语义具有以下属性:
包装器提供了一个执行方法,其行为如下:
可以从初始化上下文之外安全地访问缓存的输出。
这也可以用于初始化以及非幂等反初始化。
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
带有适当锁的块中。还有一些其他高度专业的技术,但除非绝对必要,否则我建议避免使用这些技术。
您还使用了 SHOUTY 案例,它往往表示一个static
实例方法。如果它真的是静态的,我建议你确保它不是可变的。如果创建静态不可变只是代价高昂,那么类加载无论如何都是懒惰的。您可能希望将其移动到不同的(可能是嵌套的)类,以将创建延迟到绝对最后可能的时刻。
取决于您尝试实现的目标:
如果希望所有线程共享同一个实例,可以使方法同步。这将足够
如果你想为每个线程创建一个单独的实例,你应该使用 java.lang.ThreadLocal
使用 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");
}
}
尝试定义使实例同步的方法:
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;
}