前言
本文将深入分析Java线程池的源码,包括线程池的创建、任务提交、工作线程的执行和线程池的关闭等过程。通过对线程池源码的解析,我们能够更好地理解线程池的原理和机制,为我们在实际开发中合理使用线程池提供指导。
文章内容较长,建议找个安静的环境慢慢细读。
线程池简介
概念
在传统的多线程编程中,每次需要执行任务时都会创建一个新的线程,任务执行完毕后再销毁该线程。这种方式存在一些问题,例如频繁创建和销毁线程会带来较大的开销,线程数量的不可控会导致系统资源的浪费和性能下降,所以可以采用池化技术来避免这样的性能开销。
线程池的概念包括以下几个要点:
- 线程池管理一组线程,这些线程可以被任务复用,而不是每次都创建新的线程。
- 线程池接收任务,并将任务分配给空闲的线程来执行。
- 线程池可以根据需要动态调整线程的数量,以适应不同的负载情况。
- 线程池提供了任务队列,用于存储尚未执行的任务,并根据需要进行排队和调度。
- 线程池还提供了一些管理和监控的功能,例如线程池的状态和任务执行的统计信息等
作用
线程池的作用是优化多线程应用程序的性能和可控性,它通过预先创建一组线程,并将任务提交给线程池来执行,避免了频繁创建和销毁线程的开销。线程池会根据任务的数量和系统的负载情况来动态调整线程的数量,以提高系统资源的利用率和任务的响应速度。
通过线程池,我们可以更好地管理线程的生命周期、提高系统的资源利用率,并且能够更好地控制任务的执行顺序和优先级(主要是看使用的是哪个阻塞队列实现)。因此,在开发多线程应用程序时,合理使用线程池是一种推荐的编程模式。
线程池优点
1.性能提高
线程池可以复用线程,避免了频繁创建和销毁线程的开销,从而提高了系统的性能。比较适合有大量短期的任务场景。
2.资源管理
线程池可以根据系统的负载情况动态调整线程的数量,以适应不同的负载需求,提高了系统资源的利用率。
3.控制并发度
线程池可以控制并发执行的线程数量,避免了过多的线程竞争资源导致的性能下降和系统崩溃。适用于需要限制系统并发度的场景。
4.提高响应速度
线程池可以将任务提交到任务队列中,根据需要进行排队和调度,从而提高了任务的响应速度。适合一些关心处理速度而结果可稍后获取的场景,比如异步处理、定时任务等。
5.简化编程
使用线程池可以将任务的执行和线程的管理分离,简化了多线程编程的复杂性,开发者可集中精力到业务逻辑开发中,任务的执行和管理交由线程池统一协调。
线程池适用场景
1.Web服务器
在Web服务器中,线程池可以用来处理客户端的请求。当有新的请求到达时,可以从线程池中获取一个空闲的线程来处理请求,提高服务器的并发处理能力和响应速度。
2.并发编程
在并发编程中,线程池可以用来管理并发执行的任务。通过将任务提交到线程池中,线程池会自动分配线程来执行任务,避免了频繁创建和销毁线程的开销,提高了系统的性能和资源利用率。
3.异步任务处理
在异步任务处理中,线程池可以用来执行耗时的任务,将任务的执行和结果的获取分离开来。通过将任务提交到线程池中,可以立即返回一个Future对象,通过该对象可以获取任务的执行结果,提高系统的吞吐量和响应性。
4.定时任务调度
线程池可以用来执行定时任务,通过ScheduledThreadPoolExecutor类提供的定时任务调度功能,可以在指定的时间间隔内周期性执行任务,例如定时任务的执行、定时数据的同步等。
5.后台线程处理
线程池可以用来处理一些后台线程任务,例如日志记录、数据统计等。通过将这些任务提交到线程池中,可以减少对主线程的影响,提高系统的稳定性和响应速度。
线程池核心组件
线程池的核心组件包括线程池管理器、工作线程、任务队列和拒绝策略。
1.线程池管理器(ThreadPoolExecutor)
线程池管理器负责创建和管理线程池的各个组件。它可以根据需要创建线程池,并提供方法来提交任务、关闭线程池等操作。
2.工作线程(Worker Thread)
工作线程是线程池中的实际执行任务的线程。线程池管理器会根据需要创建工作线程,并将任务分配给它们执行。工作线程执行完任务后,会继续从任务队列中获取下一个任务执行。
3.任务队列(BlockingQueue)
任务队列用于存放待执行的任务。当线程池中的工作线程执行完任务后,会从任务队列中获取下一个任务执行。任务队列可以是有界队列或无界队列(其实无界队列也是有界的,最大值是Integer.MAX_VALUE),有界队列可以限制任务的数量,避免过多的任务导致系统资源耗尽。
4.拒绝策略(RejectedExecutionHandler)
拒绝策略定义了当任务无法被线程池执行时的处理方式。当任务队列已满且无法继续添加任务时,线程池会根据拒绝策略来决定如何处理这个任务。常见的拒绝策略包括抛出异常、丢弃任务、丢弃最旧的任务或在调用者线程中直接执行任务。
这些核心组件共同协作,实现了线程池的功能。
线程池的创建
线程池的创建方式包括直接使用构造方法创建和使用线程池工厂创建两种方式。
1.构造方法创建线程池
直接创建线程池是通过实例化ThreadPoolExecutor类来创建线程池。可以使用ThreadPoolExecutor的构造函数来指定线程池的核心线程数、最大线程数、任务队列、拒绝策略等参数,然后调用execute()方法提交任务。
示例代码:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 线程空闲时间
TimeUnit.MILLISECONDS, // 时间单位
new LinkedBlockingQueue<Runnable>(), // 任务队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
executor.execute(task); // 提交任务
executor.shutdown(); // 关闭线程池
2.使用线程池工厂
线程池工厂是通过Executors类提供的静态方法来创建线程池。Executors类提供了一些常用的线程池创建方法,例如newFixedThreadPool()、newCachedThreadPool()、newSingleThreadExecutor()等。这些方法封装了线程池的创建过程,简化了线程池的配置。
示例代码:
ExecutorService executor = Executors.newFixedThreadPool(10); // 创建固定大小的线程池
executor.execute(task); // 提交任务
executor.shutdown(); // 关闭线程池
使用线程池工厂创建线程池可以方便地选择合适的线程池类型,并且无需手动配置线程池的各个参数。但是需要注意,线程池工厂创建的线程池可能不够灵活,不能满足特定的需求,而且默认使用的参数可能对不同的业务场景来说不是最优的,有可能引发性能问题。因此在一些复杂的场景下,直接通过构造方法创建ThreadPoolExecutor类可能更为适合。
线程池的使用
这里线程池使用最全参数的构造方法来创建,然后添加两个任务到线程池执行,最后关闭线程池。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolTest {
public static void main(String[] args) throws Exception {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200), new MyThreadFactory(), new MyRejectedExecutionHandler());
System.out.println(LocalDateTime.now().format(dateTimeFormatter) + "线程池创建完毕!");
// 执行任务,类型是Runnable,不带返回值
threadPoolExecutor.execute(() -> {
System.out.println(LocalDateTime.now().format(dateTimeFormatter) + Thread.currentThread().getName() + "执行完成!");
});
// 提交任务,类型是Callable,返回值是Future类型,可观测任务状态
Future<String> future = threadPoolExecutor.submit(() -> {
System.out.println(LocalDateTime.now().format(dateTimeFormatter) + Thread.currentThread().getName() + "开始执行!");
Thread.sleep(3000);
System.out.println(LocalDateTime.now().format(dateTimeFormatter) + Thread.currentThread().getName() + "执行完成并返回了值!");
return "ok";
});
System.out.println(LocalDateTime.now().format(dateTimeFormatter) + "future.isDone():" + future.isDone() + ",future.get():" + future.get());
// 休眠4秒,等future执行完成后获取结果
Thread.sleep(4000);
System.out.println(LocalDateTime.now().format(dateTimeFormatter) + "future.isDone():" + future.isDone() + ",future.get():" + future.get());
// 调用shutdown方法不再接受新任务,会将队列里的任务全部执行完成后再关闭线程池
threadPoolExecutor.shutdown();
System.out.println(LocalDateTime.now().format(dateTimeFormatter) + "线程池关闭!");
// 调用shutdownNow方法会返回线程池关闭后未执行的任务
// List<Runnable> remainder = threadPoolExecutor.shutdownNow();
}
static class MyThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "myThreadPool-thread-" + threadNumber.getAndIncrement());
}
}
static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义拒绝策略");
// 当前线程执行
r.run();
}
}
}
执行结果:
2023-07-21 18:36:40线程池创建完毕!
2023-07-21 18:36:40myThreadPool-thread-1执行完成!
2023-07-21 18:36:40myThreadPool-thread-2开始执行!
2023-07-21 18:36:43myThreadPool-thread-2执行完成并返回了值!
2023-07-21 18:36:40future.isDone():false,future.get():ok
2023-07-21 18:36:47future.isDone():true,future.get():ok
2023-07-21 18:36:47线程池关闭!
注意:future.get()方法如果线程没有执行结束,则阻塞等待直到返回结果,解释下打印结果:
“2023-07-21 18:36:43myThreadPool-thread-2执行完成并返回了值!”先于“2023-07-21 18:36:40future.isDone():false,future.get():ok”打印就是因为future.get()的阻塞等待,而fut