Guice 单身人士是否尊重线程限制? [英] Do Guice singletons honor thread-confinement?

查看:28
本文介绍了Guice 单身人士是否尊重线程限制?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我担心 Guice 以及它的单例是否会遵守我可能尝试设置的线程限制:

I have a concern regarding Guice and whether or not its singletons will obey thread confinement that I may try to set up:

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 来引导其下的整个依赖树.

So as you can see, any time we create a new instance of CacheAdaptor, a CacheModule will be used to bootstrap the entire dependency tree underneath it.

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

What happens if new CacheAdaptor(); is called from inside multiple threads?

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

For example: Thread #1 creates a new CacheAdaptor via its no-arg constructor, and Thread #2 does the same thing. Will Guice provide the same exact WidgetCache instance to each thread's CacheAdaptor, or will Guice provide 2 different instances to each thread? Even though toInstance(...) is supposed to return the same singleton instance, I'm hoping - since the modules are created inside 2 distinct threads - that each CacheAdaptor will receive a different WidgetCache instance.

提前致谢!

推荐答案

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

It's not only that Guice will provide the same singleton across threads for the same injector, but Guice can only provide the same singleton across threads if you use toInstance. Modules are evaluated once per injector, and you gave Guice one instance and no way to produce a second one.

Guice 不是魔术.当试图提供一个对象的实例时,它要么需要(1)一个 Guice-friendly no-arg 或 @Inject-annotated 构造函数;(2) Provider@Provides 方法,让你自己创建实例;或者 (3) 一个您已经创建并绑定了 toInstance 的实例,Guice 会重用它,因为它不知道如何创建另一个实例.请注意,带有 Provider 的选项 2 不需要保证每次都创建一个新实例,我们可以利用它来编写具有一个线程本地缓存.它看起来像这样:

Guice isn't magic. When trying to provide an instance of an Object, it either needs (1) a Guice-friendly no-arg or @Inject-annotated constructor; (2) a Provider or @Provides method, letting you create the instance yourself; or (3) an instance that you've already created and bound with toInstance, which Guice reuses because it doesn't know how to create another. Note that option 2, with a Provider, doesn't need to guarantee that it creates a new instance every single time, and we can exploit that to write a Provider that has a ThreadLocal cache. It would look something like this:

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,然后为每个键创建一个提供程序.这似乎有点过分,这就是自定义范围的用武之地.

Of course, if you want to do that for more than one object, you'd have to write a ThreadLocal for every single key you want to cache, and then create a provider for each. That seems a little excessive, and that's where custom scopes come in.

查看Scope唯一有意义的方法:

Check out Scope's only meaningful method:

/**
 * 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);

正如您从 作用域接口,作用域只是一个Provider的装饰器,作用域是线程局部的,相当于返回一个ThreadLocal缓存的副本如果它存在或缓存并从传递的 Provider 返回,如果它不存在.因此,我们可以轻松编写一个作用域,该作用域执行与上面手动执行的逻辑相同的逻辑.

As you can see from the Scope interface, a scope is just a decorator for a Provider, and scoping something thread-local is tantamount to returning a ThreadLocal-cached copy if it exists or caching and returning from the passed Provider if it doesn't. So we can easily write a scope that does the same logic we did manually above.

实际上,需要为每个线程创建一个新的 FooObject(对于 FooObject 的任何值)是一个常见的请求——对于基础库来说,高级功能"太多了,但对于有关如何编写自定义范围的示例.要根据您的需要调整 SimpleScope 示例,您可以省略 scope.enter()scope.exit() 调用,但保留 ThreadLocal 充当对象的线程本地缓存.

In fact, the need to create a new FooObject per-thread (for any value of FooObject) is a common request—too much of an "advanced feature" for the base library, but common enough for it to be the example about how to write a custom scope. To adjust that SimpleScope example to your needs, you can leave out the scope.enter() and scope.exit() calls, but keep the ThreadLocal<Map<Key<?>, Object>> to act as your thread-local cache of objects.

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

At that point, assuming you've created your own @ThreadScoped annotation with a ThreadScope implementation you've written, you can adjust your module to look like this:

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

Remember, singleton behavior doesn't depend on which threads you create the modules in, but rather which injector you're asking. If you created five unrelated Injector instances, they would each have their own singleton. If you're merely trying to run a small algorithm in a multi-threaded way, you could create your own injector per thread, but that way you'd lose the chance to make singleton objects that do span threads.

这篇关于Guice 单身人士是否尊重线程限制?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆