跳转至

Java 中的 Executor 和 ExecutorService 详解

简介

在 Java 编程中,多线程是一个重要的概念,它允许程序同时执行多个任务,从而提高程序的性能和响应能力。ExecutorExecutorService 是 Java 并发包 java.util.concurrent 中用于管理线程和执行任务的关键接口。它们提供了一种更高级、更灵活的方式来处理线程,比直接使用 Thread 类和 Runnable 接口更加方便和高效。本文将详细介绍 ExecutorExecutorService 的基础概念、使用方法、常见实践以及最佳实践。

目录

  1. 基础概念
    • Executor 接口
    • ExecutorService 接口
  2. 使用方法
    • 创建 ExecutorService 实例
    • 提交任务
    • 关闭 ExecutorService
  3. 常见实践
    • 固定大小线程池
    • 缓存线程池
    • 单线程线程池
    • 调度线程池
  4. 最佳实践
    • 合理选择线程池类型
    • 异常处理
    • 资源管理
  5. 小结
  6. 参考资料

基础概念

Executor 接口

Executor 是一个简单的接口,它定义了一个方法 execute(Runnable command),用于执行一个 Runnable 任务。Executor 接口的主要目的是将任务的提交和任务的执行分离,使得我们可以更加灵活地管理线程。以下是 Executor 接口的定义:

public interface Executor {
    void execute(Runnable command);
}

ExecutorService 接口

ExecutorServiceExecutor 接口的子接口,它提供了更丰富的功能,如任务的提交、线程池的管理和关闭等。ExecutorService 接口定义了一系列方法,用于提交任务、关闭线程池、获取任务执行结果等。常见的方法包括: - submit(Callable<T> task):提交一个具有返回值的任务,并返回一个 Future 对象,用于获取任务的执行结果。 - submit(Runnable task):提交一个 Runnable 任务,并返回一个 Future 对象,用于检查任务是否完成。 - shutdown():启动有序关闭,执行先前提交的任务,但不接受新任务。 - shutdownNow():尝试停止所有正在执行的活动任务,暂停处理等待中的任务,并返回等待执行的任务列表。

使用方法

创建 ExecutorService 实例

Java 提供了一个工具类 Executors,用于创建不同类型的 ExecutorService 实例。以下是一些常见的创建方式:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceExample {
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

        // 创建缓存线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        // 创建单线程线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

        // 创建调度线程池
        ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
    }
}

提交任务

创建 ExecutorService 实例后,我们可以使用 submit()execute() 方法提交任务。以下是一个简单的示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TaskSubmissionExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 提交一个 Runnable 任务
        executorService.execute(() -> {
            System.out.println("This is a Runnable task.");
        });

        // 提交一个 Callable 任务
        executorService.submit(() -> {
            System.out.println("This is a Callable task.");
            return "Task result";
        });

        executorService.shutdown();
    }
}

关闭 ExecutorService

在任务执行完毕后,我们需要关闭 ExecutorService 以释放资源。可以使用 shutdown()shutdownNow() 方法关闭线程池。以下是一个示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ShutdownExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 提交任务
        executorService.execute(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task completed.");
        });

        // 关闭线程池
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            // 等待所有任务完成
        }
        System.out.println("ExecutorService is shut down.");
    }
}

常见实践

固定大小线程池

固定大小线程池使用 Executors.newFixedThreadPool(int nThreads) 方法创建,它包含固定数量的线程。当提交的任务数量超过线程池的大小时,多余的任务将在队列中等待。以下是一个示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executorService.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " is completed.");
            });
        }

        executorService.shutdown();
    }
}

缓存线程池

缓存线程池使用 Executors.newCachedThreadPool() 方法创建,它会根据需要创建新的线程,并且会复用空闲的线程。如果线程空闲时间超过 60 秒,将被终止并从线程池中移除。以下是一个示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executorService.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " is completed.");
            });
        }

        executorService.shutdown();
    }
}

单线程线程池

单线程线程池使用 Executors.newSingleThreadExecutor() 方法创建,它只包含一个线程,任务将按照提交的顺序依次执行。以下是一个示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executorService.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " is completed.");
            });
        }

        executorService.shutdown();
    }
}

调度线程池

调度线程池使用 Executors.newScheduledThreadPool(int corePoolSize) 方法创建,它可以延迟执行任务或定期执行任务。以下是一个示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

        // 延迟 2 秒后执行任务
        scheduledExecutorService.schedule(() -> {
            System.out.println("This task is executed after 2 seconds.");
        }, 2, TimeUnit.SECONDS);

        // 延迟 1 秒后开始执行任务,之后每隔 3 秒执行一次
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("This task is executed every 3 seconds.");
        }, 1, 3, TimeUnit.SECONDS);

        // 关闭线程池
        try {
            scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduledExecutorService.shutdown();
    }
}

最佳实践

合理选择线程池类型

根据不同的业务场景,选择合适的线程池类型非常重要。例如,如果任务的执行时间较短且数量较多,可以选择缓存线程池;如果任务的执行时间较长且数量固定,可以选择固定大小线程池;如果任务需要按顺序执行,可以选择单线程线程池;如果任务需要定时执行,可以选择调度线程池。

异常处理

在任务执行过程中,可能会抛出异常。为了避免线程池中的线程因异常而终止,我们需要在任务中进行异常处理。可以使用 try-catch 块捕获异常,或者在 submit() 方法返回的 Future 对象中处理异常。以下是一个示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Future<?> future = executorService.submit(() -> {
            throw new RuntimeException("Task failed.");
        });

        try {
            future.get();
        } catch (Exception e) {
            System.out.println("Exception caught: " + e.getMessage());
        }

        executorService.shutdown();
    }
}

资源管理

在使用 ExecutorService 时,要确保在任务执行完毕后及时关闭线程池,以释放资源。可以使用 try-with-resources 语句或在 finally 块中关闭线程池。

小结

本文详细介绍了 Java 中的 ExecutorExecutorService 接口,包括基础概念、使用方法、常见实践以及最佳实践。通过使用 ExecutorExecutorService,我们可以更加灵活地管理线程和执行任务,提高程序的性能和响应能力。在实际开发中,要根据不同的业务场景选择合适的线程池类型,并注意异常处理和资源管理。

参考资料

  • 《Effective Java》(第 3 版)
  • 《Java 并发编程实战》