Java ExecutorService 示例详解
简介
在 Java 多线程编程中,ExecutorService
是一个强大的工具,用于管理和控制线程池。它提供了一种方便的方式来异步执行任务,提高应用程序的性能和响应性。本文将深入探讨 ExecutorService
的基础概念、使用方法、常见实践以及最佳实践,并通过丰富的代码示例帮助读者更好地理解和应用。
目录
- 基础概念
- 使用方法
- 创建
ExecutorService
- 提交任务
- 关闭
ExecutorService
- 创建
- 常见实践
- 固定线程池
- 缓存线程池
- 单线程池
- 最佳实践
- 线程池大小的选择
- 处理异常
- 监控线程池
- 小结
- 参考资料
基础概念
ExecutorService
是 Executor
接口的扩展,它提供了管理和控制线程池生命周期的方法。线程池是一组预先创建的线程,用于执行提交给它们的任务。使用线程池的好处包括:
- 减少线程创建和销毁的开销:避免频繁创建和销毁线程带来的性能损耗。
- 提高资源利用率:合理控制线程数量,避免过多线程导致系统资源耗尽。
- 方便管理线程:可以对线程池进行统一的管理和监控。
使用方法
创建 ExecutorService
ExecutorService
可以通过 Executors
工具类的工厂方法来创建不同类型的线程池。常见的创建方式有以下几种:
固定线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小为 3 的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交任务
for (int i = 0; i < 5; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
// 模拟任务执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
缓存线程池
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 < 5; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
// 模拟任务执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
单线程池
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++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
// 模拟任务执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
提交任务
ExecutorService
提供了多种提交任务的方法,常用的有 submit
和 execute
。
- submit
方法:可以提交实现了 Callable
或 Runnable
接口的任务,并返回一个 Future
对象,用于获取任务的执行结果。
- execute
方法:只能提交实现了 Runnable
接口的任务,没有返回值。
import java.util.concurrent.*;
public class TaskSubmissionExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 提交 Runnable 任务
executorService.execute(() -> {
System.out.println("Runnable task is running on thread " + Thread.currentThread().getName());
});
// 提交 Callable 任务
Future<String> future = executorService.submit(() -> {
System.out.println("Callable task is running on thread " + Thread.currentThread().getName());
return "Task completed";
});
try {
// 获取 Callable 任务的执行结果
String result = future.get();
System.out.println("Callable task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executorService.shutdown();
}
}
关闭 ExecutorService
当不再需要使用线程池时,应该及时关闭它,以释放资源。可以使用 shutdown
或 shutdownNow
方法来关闭线程池。
- shutdown
方法:启动一个有序关闭过程,不再接受新任务,但会继续执行已提交的任务。
- shutdownNow
方法:尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ShutdownExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 提交任务
for (int i = 0; i < 5; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException e) {
executorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
常见实践
固定线程池
固定线程池适用于任务数量已知且执行时间相对稳定的场景。例如,处理数据库批量操作、文件读写等任务。通过设置固定的线程数量,可以避免过多线程竞争资源,提高系统性能。
缓存线程池
缓存线程池适用于任务数量不确定且执行时间较短的场景。当提交的任务数超过当前线程池的活跃线程数时,会创建新的线程来执行任务。如果线程空闲时间超过 60 秒,会被自动销毁。这种线程池可以动态调整线程数量,提高资源利用率。
单线程池
单线程池适用于需要顺序执行任务的场景,例如处理一些有顺序依赖的任务,或者对共享资源进行串行访问的场景。它保证所有任务按照提交的顺序依次执行。
最佳实践
线程池大小的选择
线程池大小的选择需要综合考虑多个因素,如任务的类型(CPU 密集型、I/O 密集型)、系统的硬件资源(CPU 核心数、内存大小)等。 - CPU 密集型任务:线程池大小一般设置为 CPU 核心数 + 1,以充分利用 CPU 资源,同时避免过多线程上下文切换带来的开销。 - I/O 密集型任务:线程池大小可以设置为大于 CPU 核心数,具体数值可以根据任务的 I/O 等待时间和 CPU 处理时间的比例来调整。
处理异常
在使用 ExecutorService
时,需要注意处理任务执行过程中抛出的异常。可以通过 Future
对象的 get
方法捕获 ExecutionException
来处理任务执行过程中的异常。
import java.util.concurrent.*;
public class ExceptionHandlingExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<String> future = executorService.submit(() -> {
if (Math.random() < 0.5) {
throw new RuntimeException("Task failed");
}
return "Task completed";
});
try {
String result = future.get();
System.out.println("Task result: " + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
System.err.println("Task execution failed: " + e.getCause());
}
executorService.shutdown();
}
}
监控线程池
可以通过 ThreadPoolExecutor
类提供的方法来监控线程池的状态,如活跃线程数、已完成任务数、任务队列大小等。
import java.util.concurrent.*;
public class ThreadPoolMonitoringExample {
public static void main(String[] args) {
ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
System.out.println("Active threads: " + executorService.getActiveCount());
System.out.println("Completed tasks: " + executorService.getCompletedTaskCount());
System.out.println("Task queue size: " + executorService.getQueue().size());
executorService.shutdown();
}
}
小结
ExecutorService
是 Java 多线程编程中非常重要的工具,通过合理使用线程池,可以提高应用程序的性能和响应性。本文介绍了 ExecutorService
的基础概念、使用方法、常见实践以及最佳实践,并通过丰富的代码示例帮助读者更好地理解和应用。在实际开发中,需要根据具体的业务场景选择合适的线程池类型,并注意线程池大小的选择、异常处理和监控等方面,以确保系统的稳定性和高效性。