我对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
实例。
提前致谢!
答案 0 :(得分:19)
不仅Guice 将在同一个注入器的线程中提供相同的单例,但是如果使用{{1>,Guice 只能跨线程提供相同的单例}}。每个注入器对模块进行一次评估,你给了Guice一个实例而无法生成第二个实例。
Guice不是魔法。在尝试提供Object的实例时,它需要(1)Guice友好的无参数或toInstance
- 带注释的构造函数; (2)@Inject
或Provider
方法,让您自己创建实例;或者(3)你已经创建并与@Provides
绑定的实例,Guice重用该实例,因为它不知道如何创建另一个。请注意,带有toInstance
的选项2不需要保证它每次都创建一个新实例,我们可以利用它来编写具有ThreadLocal缓存的Provider
。它看起来像这样:
Provider
当然,如果您想为多个对象执行此操作,则必须为要缓存的每个键编写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示例,可以省略Provider
和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);
}
}
实例,则每个实例都有自己的单例。如果您只是尝试以多线程方式运行一个小算法,那么您可以为每个线程创建自己的注入器,但这样就不会有机会制作跨越线程的单例对象。