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

如您所见,每次我们创建的新实例时CacheAdaptorCacheModule都会使用a来引导其下的整个依赖关系树。

如果new CacheAdaptor();从多个线程内部调用会怎样?

例如:线程1 CacheAdaptor通过其no-arg构造函数创建了一个新线程,而线程2执行了相同的操作。
Guice将为WidgetCache每个线程的实例提供相同的确切实例CacheAdaptor,还是Guice将为每个线程提供2个不同的实例?尽管toInstance(...)应该返回相同的单例实例,但我希望-
由于模块是在2个不同的线程内创建的-每个模块都CacheAdaptor将接收不同的WidgetCache实例。

提前致谢!


问题答案:

Guice不仅 为同一注射器提供相同的单线程跨线程,而且如果您使用,Guice 只能
提供相同的单线程跨线程toInstance。每个进样器对模块进行一次评估,您给了Guice一个实例,而没有办法再生产第二个。

Guice不是魔术。尝试提供Object的实例时,它要么需要(1)一个Guice友好的无参数或带有@Inject注释的构造函数;(2)Provider@Provides方法,让您自己创建实例;或(3)您已经创建并与绑定的实例,toInstanceGuice重用了它,因为它不知道如何创建另一个实例。请注意,带有的选项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();
    }
}

当然,如果要对多个对象执行此操作,则必须为要缓存的每个键编写一个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接口可以看到,作用域只是的装饰器,对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);
    }
}

记住,单例行为并不取决于您在哪个线程中创建模块,而是取决于您要问的是哪个注入器。如果创建了五个不相关的Injector实例,则每个实例将具有自己的单例。如果您只是尝试以多线程方式运行小型算法,则可以为每个线程创建自己的注入器,但是那样您将失去创建跨线程的单例对象的机会。