一、初识线程池
我们知道,线程的创建和销毁都需要映射到操作系统,因此其代价是比较高昂的。出于避免频繁创建、销毁线程以及方便线程管理的需要,线程池应运而生。
二、线程池优势
- 降低资源消耗 :线程池通常会维护一些线程(数量为 corePoolSize),这些线程被重复使用来执行不同的任务,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了线程的频繁创建与销毁,从而降低了系统资源消耗。
- 提高响应速度 :由于线程池维护了一批 alive 状态的线程,当任务到达时,不需要再创建线程,而是直接由这些线程去执行任务,从而减少了任务的等待时间。
- 提高线程的可管理性 :使用线程池可以对线程进行统一的分配,调优和监控。
线程和任务是分离的,提高了线程的重用性
控制线程的并发数量 ,降低服务器的压力
提高了系统的响应能力
三、线程池设计思路
有句话叫做艺术来源于生活,编程语言也是如此,很多设计思想能映射到日常生活中,比如面向对象思想、封装、继承,等等。今天我们要说的线程池,它同样可以在现实世界找到对应的实体——工厂。
将任务加入到任务队列,执行任务:
当线程数小于核心线程数的时候,将任务队列里的任务加入到核心线程中并运行运行的任务调用run方法并出队,空出位置。
当核心线程满了后,开辟新的线程(非核心线程),将任务队列里的线程加入到非核心线程进行运行
当线程数大于核心线程数的时候,将任务加入到任务队列中缓存
四、ThreadPoolExecutor介绍
4.1、线程池实现类 ThreadPoolExecutor
是 Executor
框架最核心的类。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) 创建具有给定的初始参数和默认线厂和拒绝执行处理程序的一个新的 |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 创建具有给定的初始参数和默认线厂一个新的 |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) 创建一个新的 |
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 创建具有给定的初始参数的一种新 |
ThreadPoolExecutor
类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么)。
package domain03;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
*/
public class ThreadPoolExecutor {
int corePoolSize;//线程池的核心线程数量
int maximumPoolSize;//线程池的最大线程数
long keepAliveTime;//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit;//时间单位
BlockingQueue<Runnable> workQueue;//任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory;//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler;//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.keepAliveTime = keepAliveTime;
this.unit = unit;
this.workQueue = workQueue;
this.threadFactory = threadFactory;
this.handler = handler;
}
}
/**
* @program: sm
* @ClassName MyTest
* @description:
* @author: 苏芮溪
* @create: 2024−10-29 20:35
* @Version 1.0
**/
public class MyTest {
public static void main(String[] args) {
//创建线程池对象
ThreadPool tp = new ThreadPool(2,4,20);
//提交任务
for (int i = 0; i <30 ; i++) {
//任务
MyTask mt = new MyTask(i);
//提交任务
tp.submit(mt);
}
}
}
public class MyTask implements Runnable {
private int id;//任务id
public MyTask(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程"+name+"即将开始任务:"+id);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "任务执行完成" + id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
import java.util.List;
/**
@program: sm
@ClassName MyWork
@description:
@author: 苏芮溪
@create: 2024−10-29 21:29
@Version 1.0
**/
public class MyWork extends Thread {
private String name;
private List<Runnable> tasks;//后续任务放这个集合中
public MyWork(String name, List<Runnable> tasks) {
super(name);
this.tasks = tasks;
}
@Override
public void run() {
//判断如果集合中有任务 你需要执行任务
while (tasks.size()>0){
// 集合中有数据
Runnable r = tasks.remove(0);
r.run();
}
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class ThreadPool {
/*
将任务加入到任务队列
当线程数小于核心线程数的时候,将任务队列里的任务加入到核心线程中运行
当核心线程满了后,开辟新的线程(非核心线程),将任务队列里的线程加入到非核心线程进行运行
当线程数大于核心线程数的时候,将任务加入到任务队列中缓存
*/
//核心线程数
private int corePoolSize;
//最大线程数
private int maxPoolSize;
//当前线程数
private int num;
//任务队列
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
//任务队列最大长度
private int workSize;
public ThreadPool(int corePoolSize, int maxPoolSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxPoolSize = maxPoolSize;
this.workSize = workSize;
}
//执行任务run
private void runTask(Runnable r) {
//当线程数小于核心线程数的时候,将任务加入到核心线程中运行
if (num < corePoolSize) {
new MyWork("核心线程"+r,tasks).start();
num++;
}else if (num < maxPoolSize) {//当核心线程数满了后,开辟新的线程(非核心线程),将任务队列里的线程加入到非核心线程进行运行
new MyWork("非核心线程"+r,tasks).start();
num++;
}else{
System.out.println(r+"任务被缓存了........");
}
}
//提交任务
public void submit(Runnable r) {
if (tasks.size() > workSize){
System.out.println(r+"被抛出了..........");
}else{
tasks.add(r);//将任务添加到任务列表
runTask(r);//执行任务
}
}
}
五、Java线程池分类
5.1、FixedThreadPool(固定大小线程池)
FixedThreadPool是一种固定大小的线程池,它在创建时指定了线程池的核心线程数量和最大线程数量,并且这两个数量是相等的。当有任务提交到线程池中时,如果线程池中存在空闲线程,就会立即执行任务;如果线程池中没有空闲线程,就会将任务加入到任务队列中等待执行。
特点:线程数量固定,不会因为任务的增加而创建新的线程,也不会因为任务的减少而销毁线程。适用于需要限制线程数量的场景,如服务器端的连接处理。
5.2、CachedThreadPool(可缓存线程池)
CachedThreadPool是一种可缓存的线程池,它在创建时没有指定线程池的核心线程数量和最大线程数量。当有任务提交到线程池中时,如果线程池中存在空闲线程,就会立即执行任务;如果线程池中没有空闲线程,就会创建一个新的线程来执行任务。当线程在一段时间内没有执行任务时,就会被回收。
特点:线程数量不固定,可以根据任务的数量自动调整线程数量。适用于执行大量短期任务的场景,如网页爬虫。
5.3、ScheduledThreadPool(定时任务线程池)
ScheduledThreadPool是一种用于执行定时任务的线程池,它在创建时指定了线程池的核心线程数量。当有定时任务提交到线程池中时,线程池会创建一个新的线程来执行任务,并在任务执行完毕后将线程回收。如果在任务执行过程中发生异常,线程池会创建一个新的线程来替代它,并继续执行任务。
特点:可以执行定时任务和周期性任务。适用于需要定期执行任务的场景,如定时备份数据。
5.4、SingleThreadExecutor(单线程线程池)
SingleThreadExecutor是一种单线程的线程池,它在创建时只有一个核心线程。当有任务提交到线程池中时,这个核心线程会执行任务。如果任务在执行过程中发生异常,线程池会创建一个新的线程来替代它,并继续执行任务。
特点:只有一个线程在执行任务,保证任务按照提交的顺序依次执行。适用于需要保证任务顺序执行的场景,如日志记录。