热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

Whataresomerecommendedapproachestoachievingthread-safelazyinitialization?Forinstance,有哪些

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;
}

11 个解决方案

#1


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

#2


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创建的实例,则直接返回。

#3


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.

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

#4


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();
    }
}

#5


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

#6


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));
    }
}

#7


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:

顾名思义,“一次执行者”语义具有以下属性:

  1. A wrapper object which wraps a function F. In current context F is a function/lambda expression which holds the initialization/de-initialization code.
  2. 包装函数F的包装器对象。在当前上下文中,F是一个函数/ lambda表达式,它保存初始化/去初始化代码。
  3. The wrapper provides an execute method which behaves as:

    包装器提供了一个执行方法,其行为如下:

    • Calls the function F the first time execute is called and caches the output of F.
    • 在第一次调用execute时调用函数F并缓存F的输出。
    • If 2 or more threads call execute concurrently, only one “gets in” and the others block till the one which “got in” is done.
    • 如果两个或多个线程同时调用执行,则只有一个“进入”而其他线程阻塞直到“进入”的线程完成。
    • For all other/future invocations of execute, it does not call F rather simply returns the previously cached output.
    • 对于所有其他/将来的execute调用,它不会调用F而只是简单地返回先前缓存的输出。
  4. 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链接。

#8


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中的注释),但在大多数情况下,这就是我们所需要的。

#9


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,它倾向于表示静态但是实例方法。如果它是静态的,我建议你确保它没有任何可变性。如果创建静态不可变只是一个昂贵的,那么无论如何类加载都是懒惰的。您可能希望将其移动到另一个(可能是嵌套的)类,以将创建延迟到绝对最后一刻。

#10


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

#11


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;
 }

推荐阅读
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 为了在Hadoop 2.7.2中实现对Snappy压缩和解压功能的原生支持,本文详细介绍了如何重新编译Hadoop源代码,并优化其Native编译过程。通过这一优化,可以显著提升数据处理的效率和性能。此外,还探讨了编译过程中可能遇到的问题及其解决方案,为用户提供了一套完整的操作指南。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • 本文探讨了使用JavaScript在不同页面间传递参数的技术方法。具体而言,从a.html页面跳转至b.html时,如何携带参数并使b.html替代当前页面显示,而非新开窗口。文中详细介绍了实现这一功能的代码及注释,帮助开发者更好地理解和应用该技术。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 本文介绍了如何利用ObjectMapper实现JSON与JavaBean之间的高效转换。ObjectMapper是Jackson库的核心组件,能够便捷地将Java对象序列化为JSON格式,并支持从JSON、XML以及文件等多种数据源反序列化为Java对象。此外,还探讨了在实际应用中如何优化转换性能,以提升系统整体效率。 ... [详细]
  • 开发日志:201521044091 《Java编程基础》第11周学习心得与总结
    开发日志:201521044091 《Java编程基础》第11周学习心得与总结 ... [详细]
  • Java能否直接通过HTTP将字节流绕过HEAP写入SD卡? ... [详细]
  • 本文详细探讨了 jQuery 中 `ajaxSubmit` 方法的使用技巧及其应用场景。首先,介绍了如何正确引入必要的脚本文件,如 `jquery.form.js` 和 `jquery-1.8.0.min.js`。接着,通过具体示例展示了如何利用 `ajaxSubmit` 方法实现表单的异步提交,包括数据的发送、接收和处理。此外,还讨论了该方法在不同场景下的应用,如文件上传、表单验证和动态更新页面内容等,提供了丰富的代码示例和最佳实践建议。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 作为软件工程专业的学生,我深知课堂上教师讲解速度之快,很多时候需要课后自行消化和巩固。因此,撰写这篇Java Web开发入门教程,旨在帮助初学者更好地理解和掌握基础知识。通过详细记录学习过程,希望能为更多像我一样在基础方面还有待提升的学员提供有益的参考。 ... [详细]
author-avatar
落陌_漾吇
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有