线程池是一种管理和复用线程资源的机制,它由一个线程池管理器和一组工作线程组成。线程池管理器负责创建和销毁线程池,以及管理线程池中的工作线程。工作线程则负责执行具体的任务。
线程池的主要作用是管理和复用线程资源,避免了线程的频繁创建和销毁所带来的开销。
线程池包含两个重要的组成部分:
-
线程池大小:指线程池中所能容纳的最大线程数。线程池大小一般根据系统的负载情况和硬件资源来设置。
-
工作队列:用于存放等待执行的任务。当线程池中的工作线程已经全部被占用时,新的任务将被加入到工作队列中等待执行。
线程池创建方式
Java中线程池的创建方式主要有以下几种:
-
使用 ThreadPoolExecutor 类手动创建:通过 ThreadPoolExecutor 类的构造函数自定义线程池的参数,包括核心线程数、最大线程数、线程存活时间、任务队列等。
-
使用 Executors 类提供的工厂方法创建:通过 Executors 类提供的一些静态工厂方法创建线程池,例如 newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool 等。
-
使用 Spring 框架提供的 ThreadPoolTaskExecutor 类:在 Spring 框架中可以通过 ThreadPoolTaskExecutor 类来创建线程池。
不同的创建方式适用于不同的场景,通常可以根据实际情况选择合适的方式创建线程池。手动创建 ThreadPoolExecutor 类可以灵活地配置线程池参数,但需要对线程池的各项参数有一定的了解;使用 Executors 工厂方法可以快速创建线程池,但可能无法满足特定的需求,且容易出现内存溢出的情况;而 Spring 框架提供的 ThreadPoolTaskExecutor 类则只能在 Spring 框架中使用。
线程池使用
ThreadPoolExecutor 使用
ThreadPoolExecutor 线程的创建与使用示例如下:
import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { // 创建线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心线程数 5, // 最大线程数 60, // 线程池中线程的空闲时间(单位为秒) TimeUnit.SECONDS, // 时间单位 new ArrayBlockingQueue<>(10) // 任务队列 ); // 提交任务 for (int i = 1; i <= 20; i++) { final int taskId = i; executor.submit(() -> { System.out.println("执行任务:" + taskId + ",线程名称:" + Thread.currentThread().getName()); try { Thread.sleep(1000); // 模拟任务执行时间 } catch (InterruptedException e) { e.printStackTrace(); } }); } // 关闭线程池 executor.shutdown(); } }
在上面的示例中,我们通过 ThreadPoolExecutor 类手动创建了一个线程池,设置了线程池的核心线程数为 2,最大线程数为 5,线程空闲时间为 60 秒,任务队列为长度为 10 的 ArrayBlockingQueue。然后我们通过 submit() 方法向线程池中提交了 20 个任务,每个任务会在执行时输出自己的编号和执行线程的名称,然后睡眠1秒钟模拟任务执行时间。最后,我们调用 shutdown() 方法关闭线程池。
Executors 使用
import java.util.concurrent.*; public class ExecutorsDemo { public static void main(String[] args) { // 创建线程池 ExecutorService executor = Executors.newFixedThreadPool(5); // 提交任务 for (int i = 1; i <= 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("执行任务:" + taskId + ",线程名称:" + Thread.currentThread().getName()); try { Thread.sleep(1000); // 模拟任务执行时间 } catch (InterruptedException e) { e.printStackTrace(); } }); } // 关闭线程池 executor.shutdown(); } }
在上面的示例中,我们使用了 Executors 工厂类中的 newFixedThreadPool() 方法创建了一个线程池,该线程池有 5 个固定线程。然后我们通过 submit() 方法向线程池中提交了 10 个任务,每个任务会在执行时输出自己的编号和执行线程的名称,然后睡眠 1 秒钟模拟任务执行时间。最后,我们调用 shutdown() 方法关闭线程池。值得注意的是,使用 Executors 工厂类创建线程池虽然非常简单,但是在实际生产环境中并不推荐,因为这种方式很容易导致线程资源被耗尽,从而影响系统的性能和稳定性。
ThreadPoolTaskExecutor 使用
Spring 中 ThreadPoolTaskExecutor 的使用示例如下:
import java.util.concurrent.*; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; public class ThreadPoolTaskExecutorDemo { public static void main(String[] args) { // 创建线程池 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); // 核心线程数 executor.setMaxPoolSize(5); // 最大线程数 executor.setQueueCapacity(10); // 任务队列长度 executor.initialize(); // 提交任务 for (int i = 1; i <= 20; i++) { final int taskId = i; executor.submit(() -> { System.out.println("执行任务:" + taskId + ",线程名称:" + Thread.currentThread().getName()); try { Thread.sleep(1000); // 模拟任务执行时间 } catch (InterruptedException e) { e.printStackTrace(); } }); } // 关闭线程池 executor.shutdown(); } }
在上面的示例中,我们使用了 Spring 框架中的 ThreadPoolTaskExecutor 类创建了一个线程池,设置了线程池的核心线程数为 2,最大线程数为 5,任务队列长度为 10。然后我们通过 submit() 方法向线程池中提交了 20 个任务,每个任务会在执行时输出自己的编号和执行线程的名称,然后睡眠1秒钟模拟任务执行时间。最后,我们调用 shutdown() 方法关闭线程池。注意,在使用 Spring 框架中的 ThreadPoolTaskExecutor 类创建线程池时,我们需要先调用 initialize() 方法进行初始化。
ThreadPoolTaskExecutor 底层还是通过 JDK 中提供的 ThreadPoolExecutor 类实现的。
线程池优点详解
线程池相比于线程来说,它不需要频繁的创建和销毁线程,线程一旦创建之后,默认情况下就会一直保持在线程池中,等到有任务来了,再用这些已有的线程来执行任务,如下图所示:
优点1:复用线程,降低资源消耗
线程在创建时要开辟虚拟机栈、本地方法栈、程序计数器等私有线程的内存空间,而销毁时又要回收这些私有空间资源,如下图所示:
而线程池创建了线程之后就会放在线程池中,因此线程池相比于线程来说,第一个优点就是可以复用线程、减低系统资源的消耗。
优点2:提高响应速度
线程池是复用已有线程来执行任务的,而线程是在有任务时才新建的,所以相比于线程来说,线程池能够更快的响应任务和执行任务。
优点3:管控线程数和任务数
线程池提供了更多的管理功能,这里管理功能主要体现在以下两个方面:
- 控制最大并发数:线程池可以创建固定的线程数,从而避免了无限创建线程的问题。当线程创建过多时,会导致系统执行变慢,因为 CPU 核数是一定的、能同时处理的任务数也是一定的,而线程过多时就会造成线程恶意争抢和线程频繁切换的问题,从而导致程序执行变慢,所以合适的线程数才是高性能运行的关键。
- 控制任务最大数:如果任务无限多,而内存又不足的情况下,就会导致程序执行报错,而线程池可以控制最大任务数,当任务超过一定数量之后,就会采用拒绝策略来处理多出的任务,从而保证了系统可以健康的运行。
优点4:更多增强功能
线程池相比于线程来说提供了更多的功能,比如定时执行和周期执行等功能。
小结
线程池是一种管理和复用线程资源的机制。相比于线程,它具备四个主要优势:1.复用线程,降低了资源消耗;2.提高响应速度;3.提供了管理线程数和任务数的能力;4.更多增强功能。
本文已收录至《Java面试突击》,专注 Java 面试 100 年,查看更多:www.javacn.site