ExecutorService vs Casual Thread Spawner

2022-09-01 18:35:55

我有一个关于Java中如何工作的基本问题。ExecutorService

很难看出简单地创建以并行执行某些任务与将每个任务分配给 .ThreadsThreadPool

它看起来也非常简单高效,所以我想知道为什么我们不一直使用它。ExecutorService

这是否只是一种方式比另一种方式更快地执行其工作的问题?

这里有两个非常简单的例子来说明这两种方式之间的区别:

使用执行器服务:Hello World(任务)

static class HelloTask implements Runnable {
    String msg;

    public HelloTask(String msg) {
        this.msg = msg; 
    }
    public void run() {
        long id = Thread.currentThread().getId();
        System.out.println(msg + " from thread:" + id);
    }
}

使用执行器服务:Hello World(创建执行器,提交)

static class HelloTask {
    public static void main(String[] args) {
        int ntasks = 1000;
        ExecutorService exs = Executors.newFixedThreadPool(4);

        for (int i=0; i<ntasks; i++) { 
            HelloTask t = new HelloTask("Hello from task " + i);    
            exs.submit(t);
        }
        exs.shutdown();
    }
}

下面显示了一个类似的示例,但扩展了可调用接口,您能告诉我两者之间的区别以及哪种情况下应该使用特定的接口而不是另一个接口吗?

使用执行器服务:计数器(任务)

static class HelloTaskRet implements Callable<Long> {
    String msg;

    public HelloTaskRet(String msg) {
        this.msg = msg; }

        public Long call() {
        long tid = Thread.currentThread().getId(); 
        System.out.println(msg + " from thread:" + tid); 
        return tid;
    } 
}

使用执行器服务:(创建、提交)

static class HelloTaskRet {
    public static void main(String[] args) {
        int ntasks = 1000;
        ExecutorService exs = Executors.newFixedThreadPool(4);

        Future<Long>[] futures = (Future<Long>[]) new Future[ntasks];

        for (int i=0; i<ntasks; i++) { 
            HelloTaskRet t = new HelloTaskRet("Hello from task " + i);
            futures[i] = exs.submit(t);
        }
        exs.shutdown();
    }
}

答案 1

虽然问题和示例代码不相关,但我会尝试澄清两者。与随意生成线程相比,它的优点是它的行为是可预测的,并避免了线程创建的开销,这在JVM上相对较大(例如,它需要为每个线程保留内存)。通过可预测性,至少对于 ,我的意思是您知道并发线程的最大数量,并且您知道何时以及如何创建它们(因此您的JVM不会在突然达到峰值时爆炸)。ExecutorServicefixedThreadPool

作者:Vince Emigh:也支持 ,它没有最大值。人们选择使用的主要原因是防止创建多个线程(通过使用工作线程)的开销。它主要用于需要在单独的线程上执行许多小任务的情况。另外,不要忘记.ExecutorServicecachedThreadPoolExecutorServicesingleThreadExecutor

现在,关于 vs 的主题,从您的示例中很容易看出。s 可以返回一个值占位符 (),该占位符 () 最终将在将来由实际值填充。s 不能返回任何内容。RunnableCallableCallableFutureRunnable

作者:文斯·埃米格(Vince Emigh):也不能抛出异常,而可以。RunnableCallable


答案 2

与普通线程相比,执行器服务提供了许多优势

  1. 您可以创建/管理/控制线程的生命周期并优化线程创建成本开销
  2. 您可以控制任务的处理(工作窃取,ForkJoinPool,invokeAll)等。
  3. 您可以在将来的时间计划任务
  4. 您可以监视线程的进度和运行状况

即使对于单个线程,我也更喜欢使用Executors.newFixedThreadPool(1);

看看相关的 SE 问题:

Java的Fork/Join vs ExecutorService - 何时使用哪个?

使用执行器服务有哪些优势?