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

mre*_*mre 42 java thread-safety lazy-initialization

有哪些推荐的方法可以实现线程安全的延迟初始化?例如,

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

    return INSTANCE;
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*ans 53

对于单例,通过将任务委派给JVM代码进行静态初始化,可以得到一个优雅的解决方案.

public class Something {
    private Something() {
    }

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

    public static Something getInstance() {
            return LazyHolder.INSTANCE;
    }
}
Run Code Online (Sandbox Code Playgroud)

看到

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

以及Crazy Bob Lee的博客文章

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

  • 如果您的新实例需要参数,这将不会受到影响.像new Something(a,b,c)和a,b,c这样的东西通过getInstance(a,b,c)传递.在这种情况下,您必须使用维基百科中的双重空检查方法. (5认同)
  • 删除`Holder`会使`INSTANCE`一旦初始化`Something`就被初始化,从而消除了原始问题的惰性初始化要求 (2认同)

Ken*_*hoi 49

如果您正在使用Apache Commons Lang,那么您可以使用ConcurrentInitializer的一种变体,如LazyInitializer.

例:

ConcurrentInitializer<Foo> lazyInitializer = new LazyInitializer<Foo>() {

        @Override
        protected Foo initialize() throws ConcurrentException {
            return new Foo();
        }
    };
Run Code Online (Sandbox Code Playgroud)

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

Foo instance = lazyInitializer.get();
Run Code Online (Sandbox Code Playgroud)

如果你使用谷歌的番石榴:

Supplier<Foo> fooSupplier = Suppliers.memoize(new Supplier<Foo>() {
    public Foo get() {
        return new Foo();
    }
});
Run Code Online (Sandbox Code Playgroud)

然后叫它 Foo f = fooSupplier.get();

来自Suppliers.memoize javadoc:

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

  • 番石榴现在允许您使用lambda和标准的``供应商'':`Suppliers.memoize(Foo :: new)` (2认同)

Ale*_*you 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;
   }
}
Run Code Online (Sandbox Code Playgroud)

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

另一方面,这种方法是无且无等待的:如果首先进入此方法的一个线程被卡住,它将不会影响其他线程的执行.

  • +1这是唯一的建议,它不会干扰异常处理,也不会产生静态单例 (4认同)
  • 有什么理由不使用 `AtomicReference::updateAndGet` 并在 `UnaryOperator&lt;Foo&gt;` 中检查 null 并在 null 时初始化? (3认同)
  • 假设在`Foo`上有一个以上的实例是可以的,如果对`getInstance()`的早期调用严重争用可能会发生. (2认同)

JB *_*zet 9

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

public class Singleton {

    private Singleton() {
    }

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

    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
Run Code Online (Sandbox Code Playgroud)


nar*_*yan 8

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

这称为双重检查!检查这个http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html

  • 引用链接的博客 **“此代码在 Java 中不起作用。”** (5认同)
  • 这是一个正确且有效的答案。更好的来源(也在博客文章中给出):http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html (3认同)
  • 对不起!是我的错!我试图为简单的问题提供快速答案 (2认同)

int*_*21h 8

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

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

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