Java 中的 Executor 和 ExecutorService 详解
简介
在 Java 编程中,多线程是一个重要的概念,它允许程序同时执行多个任务,从而提高程序的性能和响应能力。Executor
和 ExecutorService
是 Java 并发包 java.util.concurrent
中用于管理线程和执行任务的关键接口。它们提供了一种更高级、更灵活的方式来处理线程,比直接使用 Thread
类和 Runnable
接口更加方便和高效。本文将详细介绍 Executor
和 ExecutorService
的基础概念、使用方法、常见实践以及最佳实践。
目录
- 基础概念
Executor
接口ExecutorService
接口
- 使用方法
- 创建
ExecutorService
实例 - 提交任务
- 关闭
ExecutorService
- 创建
- 常见实践
- 固定大小线程池
- 缓存线程池
- 单线程线程池
- 调度线程池
- 最佳实践
- 合理选择线程池类型
- 异常处理
- 资源管理
- 小结
- 参考资料
基础概念
Executor
接口
Executor
是一个简单的接口,它定义了一个方法 execute(Runnable command)
,用于执行一个 Runnable
任务。Executor
接口的主要目的是将任务的提交和任务的执行分离,使得我们可以更加灵活地管理线程。以下是 Executor
接口的定义:
public interface Executor {
void execute(Runnable command);
}
ExecutorService
接口
ExecutorService
是 Executor
接口的子接口,它提供了更丰富的功能,如任务的提交、线程池的管理和关闭等。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 中的 Executor
和 ExecutorService
接口,包括基础概念、使用方法、常见实践以及最佳实践。通过使用 Executor
和 ExecutorService
,我们可以更加灵活地管理线程和执行任务,提高程序的性能和响应能力。在实际开发中,要根据不同的业务场景选择合适的线程池类型,并注意异常处理和资源管理。
参考资料
- 《Effective Java》(第 3 版)
- 《Java 并发编程实战》