Guice如何将单例和非单例注入到多个线程中

3

假设我有一个类:

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使用为注入,并使用为注入,然后用于创建和执行它所传递的实例。由于它是一个缓存池,因此只有在需要但不可用时才创建新线程。

我想知道当我们将作为单例或非单例注入时,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个不同的MyTask,因此存在3个正在运行的线程。由于我们没有要求Guice将注入为单例,我认为每个线程都有其自己的注入副本,因此我们不必添加任何类似于synchronize的代码来防止3个发生冲突并导致线程问题。

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

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

如果Guice只提供FizzImpl的1个全局单例实例,那么在3个衍生线程中每个线程内部的FizzImpl "副本"(如果这是正确的词)会产生什么后果?需要注意哪些陷阱?有哪些避免这些陷阱的方法?谢谢。

5
如果你注入了 @Singleton,那么你将有三个引用指向同一个对象。我不确定这其中有什么让人惊讶或与未使用依赖注入时的情况不同之处。 - Louis Wasserman
谢谢@Louis Wasserman (+1) - 所以在这种情况下,我必须确保单例FizzImpl是线程安全的,对吗? - IAmYourFaja
2
当然。这适用于任何被多个线程引用的对象。 - Louis Wasserman
嗯,非常有趣。 - IAmYourFaja
1个回答

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() { ... }
}

然而,如果您在Fizz绑定中设置了Singleton标志,则提供程序在调用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);
    }
}

希望它能有所帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接