线程池相关介绍
一、线程池
1、概念:
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建的线程集合中自动执行这些队列任务。
2、作用:
线程池的主要作用是管理线程资源,避免频繁创建和销毁线程带来的性能开销,提高程序的执行效率和稳定性。
3、过程:
核心方法:submit(Runnable),通过Runnable描述一段要执行的任务,通过submit将任务放到线程池里面,此时线程池里面的线程就会执行这样的任务。
二、线程池参数介绍
1、核心参数
java的标准库里面提供了直接使用的线程池ThreadPoolExecutor,包含7个核心参数
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler
)
接下来我们详细介绍这7个参数。
2、corePoolSize(核心线程数)
(1)表示至少多少个线程,线程池一旦创建,这些线程也会创建,直到整个线程池销毁,这些线程才销毁。
(2)作用:线程池长期保持的线程数量,即使线程处于空闲状态也不会销毁(除非设置了allowCoreThreadTimeOut)。
(3)示例:corePoolSize=5表示线程池至少保留5个线程。
3、maximumPoolSize(最大线程数)
(1)表示线程池中的核心线程+非核心线程(不繁忙就销毁,繁忙就在创建)。
(2)作用:线程池允许创建的最大线程数量。
(3)与任务队列的关系:当核心线程满且任务队列已满时,线程池会创建新线程直到达到最大线程数。
这个关系可能有点难理解,主要和工作流程有关,下面会介绍,我们可以举一个生活中的场景来理解:
假设你经营一家餐厅,核心线程数就是长期雇佣的全职厨师,任务队列可以比作餐厅内的等待座位(例如最多坐 5 人),而最大线程数就是最多能雇佣的厨师数量。
顾客来之后,如果核心线程未满,则新顾客直接入座,全职厨师立即做菜,核心线程已满,那么新顾客进入候餐区等待,如果候餐区已满,此时餐厅会临时雇佣兼职厨师(创建新线程),直到总厨师数达到最大线程数。
最后如果总厨师数已满且候餐区已满:拒绝新顾客(触发拒绝策略)。
4、keepAliveTime(线程空闲超时时间)
(1)作用:当线程数量超过核心线程数时,多余的空闲线程在被销毁前等待新任务的最长时间。
(2)配置:需配合TimeUnit参数指定时间单位(如秒、毫秒)。
5、TimeUnit(java中的枚举类)
(1)作用:定义了7种时间单位
NANOSECONDS(纳秒)
MICROSECONDS(微秒)
MILLISECONDS(毫秒)
SECONDS(秒)
MINUTES(分钟)
HOURS(小时)
DAYS(天)
6、workQueue(任务队列)
(1) 作用:存储待执行的任务,仅当核心线程已满时,新任务才会进入队列。
(2)选择使用数组/链表;指定容量capacity;指定是否要带有优先级/比较规则;
(3)常用队列类型:
ArrayBlockingQueue:有界队列,需指定容量。
LinkedBlockingQueue:无界队列(默认容量为Integer.MAX_VALUE),可能导致 OOM。
SynchronousQueue:直接提交队列,不存储任务,需配合无界线程数使用。
7、threadFactory(线程工厂)
(1)作用:创建线程的工厂类,可自定义线程名称、优先级等属性。(统一构造并初始化线程)
(2)工厂模式也是一种设计模式,和单例模式是并列关系。
(3)工厂方法的核心是通过静态方法把构造对象new的过程、各种属性初始化过程封装起来了,提供多组静态方法实现不同的情况。
8、handler(拒绝策略)
(1)作用:当任务队列和线程池都满时,对新提交任务的处理策略。(可以结合第3点的生活场景理解)
(2)内置策略:
AbortPolicy(默认):直接抛出RejectedExecutionException,线程池可能无法继续工作
CallerRunsPolicy:让调用submit的线程自行执行任务。
DiscardPolicy:静默丢弃新任务,当前submit的这个任务。
DiscardOldestPolicy:丢弃队列中最老的任务,尝试重新提交当前任务。
三、线程池工作流程
1、线程初始化
(1)参数配置:创建线程池时需指定核心参数(如核心线程数、最大线程数、任务队列类型等)。
(2)线程创建:初始化时默认不创建线程,直到有任务提交。
2、任务提交流程
当调用submit()或execute()提交任务时,线程池按以下顺序处理:
3、任务执行与线程复用
(1)工作线程循环:线程启动后进入无限循环,从队列中获取任务并执行:
while (true) {Runnable task = workQueue.take(); // 阻塞获取任务task.run();
}
(2)线程复用:任务执行完毕后,线程不会立即销毁,而是继续等待下一个任务,实现复用。
(3)超时销毁:非核心线程空闲时间超过keepAliveTime时会被销毁,核心线程默认保留。
4、拒绝策略处理
当线程池和队列均满时,新任务会触发拒绝策略。(详见第二部分四种内置策略)
5、关闭线程池
调用shutdown()或shutdownNow()终止线程池
(1)shutdown():平缓关闭,不再接受新任务,但会执行完队列中已有的任务。
(2)shutdownNow():强制关闭,尝试中断正在执行的任务,并返回队列中未执行的任务列表。
executor.shutdown(); // 平缓关闭
try {if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {executor.shutdownNow(); // 超时未关闭则强制关闭}
} catch (InterruptedException e) {executor.shutdownNow();
}
四、使用Executors 创建常见的线程池
Executors 类提供了一系列工厂方法,用于快速创建不同类型的线程池。
返回类型是ExecutorService,通过ExecutorService.submit可以注册⼀个任务到线程池中。
1、newFixedThreadPool(固定大小线程池)
创建固定数量线程的线程池,适用于需要控制并发线程数的场景。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class FixedThreadPoolExample {public static void main(String[] args) {// 创建包含5个线程的固定大小线程池ExecutorService executor = Executors.newFixedThreadPool(5);// 提交10个任务for (int i = 0; i < 10; i++) {final int taskId = i;executor.submit(() -> {System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池executor.shutdown();}
}
特点:核心线程=最大线程数,线程数量固定
2、newCachedThreadPool(缓存线程池)
创建可根据需要动态扩展的线程池,适用于执行大量短期异步任务的场景。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CachedThreadPoolExample {public static void main(String[] args) {// 创建缓存线程池ExecutorService executor = Executors.newCachedThreadPool();// 提交大量短期任务for (int i = 0; i < 100; i++) {final int taskId = i;executor.submit(() -> {System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}});}executor.shutdown();}
}
特点:
核心线程数 = 0,最大线程数 = Integer.MAX_VALUE(理论无限)。
线程空闲 60 秒后自动回收,适用于短期任务。
使用SynchronousQueue,任务直接提交给线程处理,不排队。
风险:可能创建过多线程导致系统资源耗尽。
3、newSingleThreadExecutor(单线程线程池)
创建只有一个工作线程的线程池,确保任务按顺序执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SingleThreadExecutorExample {public static void main(String[] args) {// 创建单线程线程池ExecutorService executor = Executors.newSingleThreadExecutor();// 提交多个任务for (int i = 0; i < 5; i++) {final int taskId = i;executor.submit(() -> {System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}executor.shutdown();}
}
特点:
核心线程数 = 最大线程数 = 1,确保任务串行执行。
使用无界队列,任务按提交顺序执行。
适用于需要顺序执行任务且不允许并发的场景。
4、newScheduledThreadPool(定时任务线程池)
创建支持定时或周期性执行任务的线程池。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolExample {public static void main(String[] args) {// 创建包含3个线程的定时任务线程池ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);// 延迟2秒后执行一次任务executor.schedule(() -> {System.out.println("Delayed task executed");}, 2, TimeUnit.SECONDS);// 延迟1秒后,每3秒执行一次任务executor.scheduleAtFixedRate(() -> {System.out.println("Periodic task executed");}, 1, 3, TimeUnit.SECONDS);}
}
特点:核心线程数固定,最大线程数为Integer.MAX_VALUE。
支持两种调度方式:
schedule():延迟执行一次任务。
scheduleAtFixedRate()/scheduleWithFixedDelay():周期性执行任务。
总结:Executors 提供的工厂方法能快速创建线程池,但需根据实际场景谨慎选择。对于生产环境,建议手动配置线程池参数,避免使用默认的无界队列和无限线程数,以防止系统资源耗尽和内存溢出。