不仅 Guice将为同一个注入器跨线程提供相同的单例,而且如果您使用toInstance
. 每个注入器对模块进行一次评估,您给 Guice 一个实例,而无法生成第二个实例。
Guice 不是魔法。当试图提供一个对象的实例时,它要么需要(1)一个 Guice 友好的无参数或带注释的@Inject
构造函数;(2) Provider
or@Provides
方法,让你自己创建实例;或 (3) 您已经创建并绑定的实例,toInstance
Guice 会重复使用该实例,因为它不知道如何创建另一个。请注意,带有 a 的选项 2Provider
不需要保证它每次都创建一个新实例,我们可以利用它来编写Provider
具有 ThreadLocal 缓存的 a。它看起来像这样:
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 接口中看到的那样,作用域只是 a 的装饰器Provider
,并且将某些线程局部范围限定为如果存在则返回ThreadLocal
-cached 副本,或者如果不存在则缓存并从传递的副本返回Provider
。因此,我们可以轻松地编写一个执行与我们在上面手动执行的逻辑相同的逻辑的范围。
事实上,需要为每个线程(对于 FooObject 的任何值)创建一个新的 FooObject 是一个常见的请求——<a href="https://code.google.com/p/google-guice/issues/detail? id=114" rel="noreferrer">对于基础库来说,“高级功能”太多了,但对于如何编写自定义范围的示例来说已经足够普遍了。要根据您的需要调整 SimpleScope 示例,您可以省略scope.enter()
andscope.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);
}
}
请记住,单例行为并不取决于您在哪个线程中创建模块,而是取决于您要询问的注入器。如果您创建了五个不相关Injector
的实例,它们每个都有自己的单例。如果您只是尝试以多线程方式运行一个小算法,您可以为每个线程创建自己的注入器,但这样您将失去创建跨线程的单例对象的机会。