Guice单身人士是否尊重线程限制?

时间:2013-09-03 19:20:29

标签: 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执行相同的操作。 Will Guice会为每个帖子的WidgetCache提供相同的CacheAdaptor实例,还是Guice会为每个帖子提供2个不同的实例?即使toInstance(...)应该返回同一个单例实例,我希望 - 因为模块是在2个不同的线程内创建的 - 每个CacheAdaptor将收到一个不同的WidgetCache实例。

提前致谢!

1 个答案:

答案 0 :(得分:19)

不仅Guice 在同一个注入器的线程中提供相同的单例,但是如果使用{{1>,Guice 只能跨线程提供相同的单例}}。每个注入器对模块进行一次评估,你给了Guice一个实例而无法生成第二个实例。

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

Provider

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

创建自己的ThreadLocal范围

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

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

正如您在Scope interface中看到的那样,范围只是/** * 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); 的装饰器,并且某些线程本地范围等同于返回Provider - 缓存副本(如果它)存在或缓存并从传递的ThreadLocal返回(如果不存在)。因此,我们可以轻松编写一个与我们手动执行相同逻辑的范围。

事实上,需要为每个线程创建一个新的FooObject(对于FooObject的任何值)是一个常见的请求 - too much of an "advanced feature" for the base library,但它足以使它成为example about how to write a custom scope。要根据需要调整SimpleScope示例,可以省略Providerscope.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); } } 实例,则每个实例都有自己的单例。如果您只是尝试以多线程方式运行一个小算法,那么您可以为每个线程创建自己的注入器,但这样就不会有机会制作跨越线程的单例对象。