跳转至

Java ExecutorService 示例详解

简介

在 Java 多线程编程中,ExecutorService 是一个强大的工具,用于管理和控制线程池。它提供了一种方便的方式来异步执行任务,提高应用程序的性能和响应性。本文将深入探讨 ExecutorService 的基础概念、使用方法、常见实践以及最佳实践,并通过丰富的代码示例帮助读者更好地理解和应用。

目录

  1. 基础概念
  2. 使用方法
    • 创建 ExecutorService
    • 提交任务
    • 关闭 ExecutorService
  3. 常见实践
    • 固定线程池
    • 缓存线程池
    • 单线程池
  4. 最佳实践
    • 线程池大小的选择
    • 处理异常
    • 监控线程池
  5. 小结
  6. 参考资料

基础概念

ExecutorServiceExecutor 接口的扩展,它提供了管理和控制线程池生命周期的方法。线程池是一组预先创建的线程,用于执行提交给它们的任务。使用线程池的好处包括: - 减少线程创建和销毁的开销:避免频繁创建和销毁线程带来的性能损耗。 - 提高资源利用率:合理控制线程数量,避免过多线程导致系统资源耗尽。 - 方便管理线程:可以对线程池进行统一的管理和监控。

使用方法

创建 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 提供了多种提交任务的方法,常用的有 submitexecute。 - submit 方法:可以提交实现了 CallableRunnable 接口的任务,并返回一个 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

当不再需要使用线程池时,应该及时关闭它,以释放资源。可以使用 shutdownshutdownNow 方法来关闭线程池。 - 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 的基础概念、使用方法、常见实践以及最佳实践,并通过丰富的代码示例帮助读者更好地理解和应用。在实际开发中,需要根据具体的业务场景选择合适的线程池类型,并注意线程池大小的选择、异常处理和监控等方面,以确保系统的稳定性和高效性。

参考资料