Are the leagues keeping the thread alone?

I have concerns about Guice and whether its singleton will obey the flow restriction that I can try to tweak:

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

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

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

For example: Thread # 1 creates a new CacheAdaptor via the no-arg constructor, and Thread # 2 does the same. Will Guice provide the same exact WidgetCache instance for each CacheAdaptor stream, or will Guice provide two different instances for each stream? Even if toInstance(...) should return the same singleton instance, I'm Pending - since modules are created inside two separate threads - each CacheAdaptor will receive a different instance of WidgetCache .

Thanks in advance!

+4
source share
1 answer

Not only will Guice provide the same singleton for threads for the same injector, but Guice can only provide the same singleton for threads if you use toInstance . Modules are evaluated once per injector, and you provided Guice with one instance and could not create a second.

Guice is not magic. When trying to provide an instance of an object, it requires (1) a constructor without arguments, no arguments, or @Inject -annotated; (2) a Provider or @Provides , allowing you to instantiate yourself; or (3) an instance that you have already created and associated with toInstance , which Guice repeats because it does not know how to create another. Note that option 2 with the Provider does not have to guarantee that it creates a new instance each time, and we can use this to write the Provider with the ThreadLocal cache. It will 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(); } } 

Of course, if you want to do this for more than one object, you will have to write ThreadLocal for each individual key that you want to cache, and then create a provider for each. This seems a little excessive, and that is where special areas appear.

Creating Your Own ThreadLocal Area

Mark the scope of only the 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 {@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); 

As you can see from the Scope interface , the scope is just a decorator for the Provider , and the scope is a local thread equivalent to returning a copy of ThreadLocal -cached if it exists or is cached and returned from the passed Provider if it is not. Therefore, we can easily write a scope that does the same logic as us manually.

In fact, the need to create a new FooObject for the stream (for any FooObject value) is a common request for the "extended function" "too large for the base library , but a fairly common example for him on how to write a custom scope . To adapt this SimpleScope example to your needs , you can leave calls to scope.enter() and scope.exit() , but keep ThreadLocal<Map<Key<?>, Object>> as your local object cache.

At this point, assuming that you created your own @ThreadScoped annotation with the ThreadScope implementation you wrote, you can configure your module this way:

 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); } } 

Remember that single-user behavior does not depend on which threads you create in the modules, but rather which injector you ask. If you created five unrelated Injector instances, each of them will have its own singleton. If you are just trying to run a small algorithm in a multi-threaded way, you can create your own injector for each thread, but in this way you will lose the ability to create single objects that span the threads.

+17
source

Source: https://habr.com/ru/post/1214395/


All Articles