9

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

例如:线程#1CacheAdaptor通过它的无参数构造函数创建一个新的,线程#2 做同样的事情。Guice 会为每个线程提供完全相同的WidgetCache实例CacheAdaptor,还是 Guice 会为每个线程提供 2 个不同的实例?尽管toInstance(...)应该返回相同的单例实例,但我希望——因为模块是在 2 个不同的线程中创建的——每个模块CacheAdaptor都会收到一个不同的WidgetCache实例。

提前致谢!

4

1 回答 1

25

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

Guice 不是魔法。当试图提供一个对象的实例时,它要么需要(1)一个 Guice 友好的无参数或带注释的@Inject构造函数;(2) Provideror@Provides方法,让你自己创建实例;或 (3) 您已经创建并绑定的实例,toInstanceGuice 会重复使用该实例,因为它不知道如何创建另一个。请注意,带有 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的实例,它们每个都有自己的单例。如果您只是尝试以多线程方式运行一个小算法,您可以为每个线程创建自己的注入器,但这样您将失去创建跨线程的单例对象的机会。

于 2013-09-03T20:49:17.757 回答