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

IAm*_*aja 5 java singleton dependency-injection 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.
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,每当我们创建一个新实例时CacheAdaptor,CacheModule都会使用它来引导它下面的整个依赖关系树.

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

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

提前致谢!

Jef*_*ica 22

Guice不仅为同一个注入器提供跨线程的相同单例,而且Guice 只能在你使用的线程中提供相同的单例toInstance.模块每个注入器评估一次,你给Guice一个实例,没有办法产生第二个实例.

Guice并不神奇.当试图提供一个Object的实例时,它需要(1)一个Guice友好的无参数或@Inject注释构造函数; (2)a Provider@Provides方法,让你自己创建实例; 或者(3)你已经创建并绑定的实例,toInstanceGuice重用它,因为它不知道如何创建另一个.注意,带有a的选项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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,如果要为多个对象执行此操作,则必须为要缓存的每个键编写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);
Run Code Online (Sandbox Code Playgroud)

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

实际上,需要为每个线程创建一个新的FooObject(对于FooObject的任何值)是一个常见的请求 - 对于基础库来说太多的"高级功能",但通常足以让它成为如何示例写一个自定义范围.要根据需要调整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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请记住,单例行为不依赖于您创建模块的线程,而是依赖于您要求的注入器.如果您创建了五个不相关的Injector实例,则每个实例都有自己的单例.如果您只是尝试以多线程方式运行一个小算法,那么您可以为每个线程创建自己的注入器,但这样您就不会有机会制作跨越线程的单个对象.