java - Guice 单例是否支持线程限制?

标签 java dependency-injection singleton guice

我担心 Guice 及其单例是否会遵守我可能尝试设置的线程限制:

public class CacheModule extends AbstractModule {
    @Override
    protected void configure() {
        // WidgetCache.class is located inside a 3rd party JAR that I
        // don't have the ability to modify.
        WidgetCache widgetCache = new WidgetCache(...lots of params);

        // Guice will reuse the same WidgetCache instance over and over across
        // multiple calls to Injector#getInstance(WidgetCache.class);
        bind(WidgetCache.class).toInstance(widgetCache);
    }
}

// CacheAdaptor is the "root" of my dependency tree. All other objects
// are created from it.
public class CacheAdaptor {
    private CacheModule bootstrapper = new CacheModule();

    private WidgetCache widgetCache;

    public CacheAdaptor() {
        super();

        Injector injector = Guice.createInjector(bootstrapper);

        setWidgetCache(injector.getInstance(WidgetCache.class));
    }

    // ...etc.
}

如您所见,每当我们创建 CacheAdaptor 的新实例时,CacheModule 将用于引导其下的整个依赖树。

如果从多个线程内部调用 new CacheAdaptor(); 会发生什么情况?

例如:线程 #1 通过其无参数构造函数创建一个新的 CacheAdaptor,线程 #2 做同样的事情。 Guice 会为每个线程的 CacheAdaptor 提供完全相同的 WidgetCache 实例,还是 Guice 会为每个线程提供 2 个不同的实例? 即使 toInstance(...) 应该返回相同的单例实例,我希望 - 因为模块是在 2 个不同的线程中创建的 - 每个 CacheAdaptor 将收到不同的 WidgetCache 实例。

最佳答案

不仅 Guice 为相同的注入(inject)器提供相同的跨线程单例,而且如果您使用 toInstance,Guice 只能提供相同的跨线程单例。 .每个注入(inject)器对模块进行一次评估,并且您给了 Guice 一个实例,但无法生成第二个实例。

Guice 不是魔法。当试图提供一个对象的实例时,它要么需要 (1) 一个 Guice 友好的无参数或 @Inject -带注释的构造函数; (2) Provider@Provides方法,让你自己创建实例;或 (3) 您已经创建并绑定(bind)了 toInstance 的实例,Guice 重复使用它,因为它不知道如何创建另一个。请注意选项 2,带有 Provider , 不需要保证它每次都创建一个新实例,我们可以利用它来编写 Provider有一个 ThreadLocal 缓存。它看起来像这样:

public class CacheModule extends AbstractModule {
    /** This isn't needed anymore; the @Provides method below is sufficient. */
    @Override protected void configure() {}

    /** This keeps a WidgetCache per thread and knows how to create a new one. */
    private ThreadLocal<WidgetCache> threadWidgetCache = new ThreadLocal<>() {
        @Override protected WidgetCache initialValue() {
            return new WidgetCache(...lots of params);
        }
    };

    /** Provide a single separate WidgetCache for each thread. */
    @Provides WidgetCache provideWidgetCache() {
        return threadWidgetCache.get();
    }
}

当然,如果您想对多个对象执行此操作,则必须为要缓存的每个键都编写一个 ThreadLocal,然后为每个键创建一个提供程序。这似乎有点过分,这就是自定义范围的用武之地。

创建自己的 ThreadLocal 作用域

查看 Scope 唯一有意义的方法:

/**
 * Scopes a provider. The returned provider returns objects from this scope.
 * If an object does not exist in this scope, the provider can use the given
 * unscoped provider to retrieve one.
 *
 * <p>Scope implementations are strongly encouraged to override
 * {@link Object#toString} in the returned provider and include the backing
 * provider's {@code toString()} output.
 *
 * @param key binding key
 * @param unscoped locates an instance when one doesn't already exist in this
 *  scope.
 * @return a new provider which only delegates to the given unscoped provider
 *  when an instance of the requested object doesn't already exist in this
 *  scope
 */
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped);

Scope interface可以看出, 作用域只是 Provider 的装饰器, 并且限定线程局部的东西等同于返回 ThreadLocal -缓存副本(如果存在)或缓存并从传递的 Provider 返回如果没有。因此,我们可以轻松地编写一个作用域来执行我们在上面手动执行的相同逻辑。

事实上,需要为每个线程(对于 FooObject 的任何值)创建一个新的 FooObject 是一个常见的请求— too much of an "advanced feature" for the base library , 但足够常见以至于它是 example about how to write a custom scope .要根据您的需要调整 SimpleScope 示例,您可以省略 scope.enter()scope.exit()电话,但保留 ThreadLocal<Map<Key<?>, Object>>充当对象的线程本地缓存。

此时,假设您已经创建了自己的 @ThreadScoped带有 ThreadScope 的注释在您编写的实现中,您可以将模块调整为如下所示:

public class CacheModule extends AbstractModule {
    @Override
    protected void configure() {
        bindScope(ThreadScoped.class, new ThreadScope());
    }

    /** Provide a single separate WidgetCache for each thread. */
    @Provides @ThreadScoped WidgetCache provideWidgetCache() {
        return new WidgetCache(...lots of params);
    }
}

请记住,单例行为并不取决于您在哪个线程中创建模块,而是取决于您请求的是哪个注入(inject)器。如果您创建了五个不相关的 Injector例如,他们每个人都有自己的单例。如果您只是想以多线程方式运行一个小算法,您可以为每个线程创建自己的注入(inject)器,但那样您将失去创建跨线程的单例对象的机会。

关于java - Guice 单例是否支持线程限制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18599871/

相关文章:

java - 如何启用/禁用 ToggleButton 的声音?

java - @Autowired 对象中的 Spring 空指针异常

Java Bean 复合注入(inject)

c++ - 线程本地单例

asp.net-core - 何时使用以下 transient 、作用域和单例

java - 通过枚举方式的单例是惰性初始化的吗?

java - 线程 "main"java.awt.AWTError : BoxLayout can't be shared 中的异常

java - 使用 BufferReader 和 BufferedWriter 写入文件时,文件创建为空

c# - 如何在 Autofac 上注册单例

java - 分布式环境下如何实现Spring bean单例?