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();会发生什么? 从多个线程内部调用?

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

提前致谢!

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

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

 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 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. * * 

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 Provider scope(Key key, Provider unscoped);

正如你在Scope接口中看到的那样,作用域只是Provider一个装饰器,并且stroping一些thread-local就等于返回一个ThreadLocal -cached副本(如果它存在)或缓存并从传递的Provider返回(如果它没有) 。 因此,我们可以轻松编写一个范围,该范围与我们上面手动执行的逻辑相同。

实际上,需要为每个线程创建一个新的FooObject(对于FooObject的任何值)是一个常见的请求 – 对于基础库来说太多的“高级function” ,但通常足以让它成为关于如何使用的示例写一个自定义范围 。 要根据需要调整SimpleScope示例,可以scope.enter()scope.exit()调用,但保持ThreadLocal, Object>>充当线程本地缓存对象。

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

 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实例,它们每个都有自己的单例。 如果您只是尝试以multithreading方式运行一个小算法,则可以为每个线程创建自己的注入器,但这样您就不会有机会创建跨越线程的单个对象。