【问题标题】:How Guice injects singletons and non-singletons into multiple threadsGuice 如何将单例和非单例注入多个线程
【发布时间】:2013-07-29 15:44:33
【问题描述】:

假设我有一堂课:

public class MyTask implements Runnable {
    @Inject
    private Fizz fizz;

    // Getters and setters for 'fizz'.

    @Override
    public void run() {
        if(fizz.alleviatesBuzz())
            doA();
        else
            doB();
    }

    private void doA() { ... }

    private void doB() { ... }
}

然后我还有另一堂课:

public class MyTaskDispatcher {
    @Inject
    private ThreadFactory threadFactory;

    private Executor executor;

    // Getter and setter for 'threadFactory'.

    public void dispatch(MyTask task) {
        if(executor == null)
            executor = Executors.newCachedThreadPool(threadFactory);

        executor.submit(task);
    }
}

因此,Guice 用Fizz 注入MyTask,并用ThreadFactory 注入MyTaskDispatcher,然后用于创建和执行传递给它的MyTask 实例。而且,由于它是一个缓存池,它只会在需要但不可用时创建一个新线程。

我想知道当我们将 Fizz 作为单例或非单例注入时,Guice 在多线程环境中的“行为”如何。

让我们从非单例实例开始:

public class MyAppModule extends AbstractModule {
    @Override
    public void configure() {
        bind(Fizz.class).to(FizzImpl.class);

        // I don't think the functionality of MyThreadFactory
        // really matters for the sake of this question.
        bind(ThreadFactory.class).to(MyThreadFactory.class);
    }

    @Provides
    FizzImpl providesFizz() {
        return new FizzImpl(true, Buzz.ALWAYS, 35);
    }

    // I *believe* we always want the ThreadFactory to be singleton,
    // because all of the threads spawn from it and its executor.
    @Provides @Singleton
    ThreadFactory providesThreadFactory() {
        return new MyThreadFactory(12);
    }
}

现在假设应用程序已经运行了一段时间,并且已经提交了 3 个单独的 MyTasks,因此存在 3 个运行线程。由于我们没有要求 Guice 将 Fizzes 作为单例注入,我假设每个线程都有自己注入的 FizzImpl 的副本,我们不必添加任何 synchronize 类型的代码来防止3 FizzImpls 因冲突而导致线程问题。

但是当我们让 Guice 将 FizzImpl 作为单例注入时会发生什么?!? 现在,在 MyAppModule 中:

    @Provides @Singleton
    FizzImpl providesFizz() {
        return new FizzImpl(true, Buzz.ALWAYS, 35);
    }

如果 Guice 只提供了 1 个全局的 FizzImpl 的单例实例,那么在 3 个生成的线程中的每个 FizzImpl“副本”(如果这是正确的词的话)的下游分支是什么? 有哪些需要注意的陷阱?有什么方法可以对抗这些陷阱?提前致谢。

【问题讨论】:

  • 如果您要注入 @Singleton,那么您将拥有对同一对象的三个引用。我不确定这与不使用 DI 会发生什么令人惊讶或不同。
  • 感谢@Louis Wasserman (+1) - 所以在这种情况下,我必须确保单例 FizzImpl 是线程安全的,是吗?
  • 当然。多线程引用的任何对象都是如此。
  • 嗯。非常有趣。

标签: java multithreading concurrency singleton guice


【解决方案1】:

不,Fizz 将使用 MyTask 实例创建,并且它将在多个线程调用中持续存在。如果您想为每个线程创建一个 Fizz 副本,则必须以惰性方式进行。

public class MyTask implements Runnable {
    @Inject
    private Provider<Fizz> fizzProvider;

    // Getters and setters for 'fizz'.

    @Override
    public void run() {
        Fizz fizz = fizzProvider.get();
        if(fizz.alleviatesBuzz())
            doA();
        else
            doB();
    }

    private void doA() { ... }

    private void doB() { ... }
}

但是,如果您将 Singleton 标志放入 Fizz 绑定,则在您调用 fizzProvider.get() 时提供程序将返回相同的实例,因此所有线程将具有相同的实例。你必须保持它不是单例的。

另外你的模块是错误的,你应该使用方法或隐式绑定,而不是两者。你也不能提供实例并注入它的接口。

public class MyAppModule extends AbstractModule {
    @Override
    public void configure() {
        bind(Fizz.class).to(FizzImpl.class);
        //or bind(Fizz.class).toInstance(new FizzImpl(true, Buzz.ALWAYS, 35)); //Singleton!!
        //or bind(Fizz.class).toProvider(new Provider<Fizz>() {
        //      @Override
        //      public Subject get() {
        //        return new FizzImpl(true, Buzz.ALWAYS, 35);
        //      }
        //    });

        // I don't think the functionality of MyThreadFactory
        // really matters for the sake of this question.
        bind(ThreadFactory.class).to(MyThreadFactory.class);
    }
}

public class MyAppModule extends AbstractModule {
    @Override
    public void configure() {
    }

    @Provides
    Fizz providesFizz() {
        return new FizzImpl(true, Buzz.ALWAYS, 35);
    }

    // I *believe* we always want the ThreadFactory to be singleton,
    // because all of the threads spawn from it and its executor.
    @Provides @Singleton
    ThreadFactory providesThreadFactory() {
        return new MyThreadFactory(12);
    }
}

希望对您有所帮助!

【讨论】: