如何使用裸 ThreadPoolExecutor 获得 MoreExecutors.newDirectExecutorService() 行为?

How to get MoreExecutors.newDirectExecutorService() behavior by using bare ThreadPoolExecutor?

当我运行以下代码时:

package foo.trials;

import com.google.common.util.concurrent.MoreExecutors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DirectExecutorService {
    private static final Logger logger_ = LoggerFactory.getLogger(DirectExecutoService.class);

    public static void main(String[] args) {
        boolean useGuava = true;

        final ExecutorService directExecutorService;
        if (useGuava) {
            directExecutorService = MoreExecutors.newDirectExecutorService();
        } else {
            directExecutorService = new ThreadPoolExecutor(
                    0, 1, 0, TimeUnit.DAYS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadPoolExecutor.CallerRunsPolicy());
            directExecutorService.submit(new BlockingCallable());
        }

        Future<Boolean> future = directExecutorService.submit(new MyCallable());
        try {
            logger_.info("Result: {}", future.get());
        } catch (InterruptedException e) {
            logger_.error("Unexpected: Interrupted!", e);
        } catch (ExecutionException e) {
            logger_.error("Unexpected: Execution exception!", e);
        }
        logger_.info("Exiting...");
    }

    static class MyCallable implements Callable<Boolean> {
        static final Random _random = new Random();
        @Override
        public Boolean call() throws Exception {
            logger_.info("In call()");
            return _random.nextBoolean();
        }
    }

    static class BlockingCallable implements Callable<Boolean> {

        Semaphore semaphore = new Semaphore(0);
        @Override
        public Boolean call() throws Exception {
            semaphore.acquire(); // this will never succeed.
            return true;
        }
    }
}

我得到以下输出

13:36:55.960 [main] INFO  a.t.DirectExecutoService - In call()
13:36:55.962 [main] INFO  a.t.DirectExecutoService - Result: true
13:36:55.963 [main] INFO  a.t.DirectExecutoService - Exiting...

请注意,所有执行都发生在 main 线程中。特别是在调用线程中调度可调用对象的调用。当然,这是人们对 MoreExecutors.newDirectExecutorService() 的期望,这不足为奇。

当我将变量 useGuava 设置为 false 时,我得到了类似的结果。

13:45:14.264 [main] INFO  a.t.DirectExecutoService - In call()
13:45:14.267 [main] INFO  a.t.DirectExecutoService - Result: true
13:45:14.268 [main] INFO  a.t.DirectExecutoService - Exiting...

但是如果我注释掉下面这行

directExecutorService.submit(new BlockingCallable());

然后我得到以下输出。

13:37:27.355 [pool-1-thread-1] INFO  a.t.DirectExecutoService - In call()
13:37:27.357 [main] INFO  a.t.DirectExecutoService - Result: false
13:37:27.358 [main] INFO  a.t.DirectExecutoService - Exiting...

正如你所看到的,可调用对象的调用发生在另一个线程中 pool-1-thread-1。我想我可以解释为什么会这样;也许是因为线程池可以有(最多)1 个可用线程,所以第一个 Callable 被分派到那个额外的线程,否则该线程被 BlockingCallable.

消耗

我的问题是如何创建一个 ExecutorService 来完成 DirectExecutorService 所做的事情,而不必人为地使用永远不会完成的可调用线程来燃烧线程?

我为什么要问这个?

  1. 我有一个使用 11.0 版番石榴的代码库。我需要避免将其升级到 17.0+——它提供 MoreExecutors.newDirectExecutorService()——如果可以的话。
  2. ThreadPoolExecutor 不允许将 maxThreads 设置为 0。如果它允许这样做会很奇怪,但如果它允许的话那也解决了我的问题。
  3. 最后,我很惊讶地注意到这种行为——我曾(错误地)假设使用 CallerRunsPolicy 会导致 all 的所有 call Callables 将在调用者的线程中执行。所以,我想把我的经验和 hack 放在那里,这样其他人就可以节省最终在试图理解这一点时耗费的时间。 :(

如果无法升级到 Guava 17.0+,是否有 better/more 惯用的方法来实现类似 DirectExecutorService 的行为?

Is there better/more idiomatic way to achieve DirectExecutorService like behavior if one can't upgrade to guava 17.0+?

如果这是你唯一的问题,你应该使用 MoreExecutors.sameThreadExecutor(). It's basically newDirectExecutorService() before it was moved to a new method (and directExecutor() was added), see Javadoc:

Since: 18.0 (present as MoreExecutors.sameThreadExecutor() since 10.0)

顺便说一句:你真的应该升级到最新的番石榴,你用的是快六岁的番石榴了!