【问题标题】:ExecutorService vs Casual Thread SpawnerExecutorService vs Casual Thread Spawner
【发布时间】:2015-01-12 07:54:39
【问题描述】:

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

很难看出简单地创建Threads 以并行执行一些任务和将每个任务分配给ThreadPool 之间的区别。

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

下面展示了一个类似的例子,但扩展了 Callable 接口,你能告诉我两者之间的区别吗?在哪些情况下应该使用特定的而不是另一个?

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

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

【问题讨论】:

  • 两个示例都使用ExecutorService 而不是创建新线程,所以我不确定在这种情况下您在比较两个示例之间的内容。您是否对何时使用Runnable 以及何时使用Callable 感到困惑?

标签: java multithreading executorservice


【解决方案1】:

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

文斯·艾米: ExecutorService 也支持cachedThreadPool,它没有 最大限度。人们选择使用ExecutorService 的主要原因是 防止创建多个线程的开销(通过使用 worker 线程)。它主要用于需要执行许多小任务的情况 在单独的线程上执行。还有,别忘了 singleThreadExecutor.

现在,关于RunnableCallable 的话题,从您的示例中很容易看出。 Callables 可以返回一个值占位符 (Future),该值占位符最终将由未来的实际值填充。 Runnables 不能返回任何东西。

文斯·艾米: Runnable 也不能抛出异常,而 Callable 可以。

【讨论】:

    【解决方案2】:

    ExecutorService 与普通线程相比具有许多优势

    1. 您可以创建/管理/控制线程的生命周期并优化线程创建成本开销
    2. 您可以控制任务的处理(Work Stealing、ForkJoinPool、invokeAll)等。
    3. 您可以在未来时间安排任务
    4. 您可以监控线程的进度和健康状况

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

    查看相关的 SE 问题:

    Java's Fork/Join vs ExecutorService - when to use which?

    What are the advantages of using an ExecutorService?

    【讨论】:

      猜你喜欢
      • 2020-12-21
      • 2019-02-17
      • 1970-01-01
      • 1970-01-01
      • 2019-09-27
      • 2010-09-29
      • 1970-01-01
      • 2011-01-11
      • 2017-02-13
      相关资源
      最近更新 更多