什么是线程池
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象;
为什么使用线程池
使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行压力;当然了,使用线程池的原因不仅仅只有这些,我们可以从线程池自身的优点上来进一步了解线程池的好处;
使用线程池有哪些优势
- 线程和任务分离,提升线程重用性;
- 控制线程并发数量,降低服务器压力,统一管理所有线程;
- 提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;
ThreadPoolExecutor 线程池类
public ThreadPoolExecutor(
int corePoolSize, //核心线程数量
int maximumPoolSize,// 最大线程数
long keepAliveTime, // 最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 饱和处理机制
) {…}
ThreadPoolExecutor参数详解
我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数
-
a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0),于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程);
-
在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2);
-
紧接着在a,b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候,并告知他: 如果1、2号工作人员空出,c就可以前去办理业务;
-
此时d客户又到了银行,(工作人员都在忙,大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给d客户办理业务;
-
假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数量就是最大线程数),于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒接接待e客户;
-
最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间),经理就会让这部分空闲的员工人下班.(销毁线程)但是为了保证银行银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保持核心线程数量);
自定义线程池-实现步骤(模拟ThreadPoolExecutor工作原理)
- 编写任务类(MyTask),实现Runnable接口;
- 编写线程类(MyWorker),用于执行任务,需要持有所有任务;
- 编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;
- 编写测试类(MyTest),创建线程池对象,提交多个任务测试;
MyTask
package com.boot.bootdbbatis.excutor.demo1;
/**
* 自定义线程池练习,这是任务类,需要实现Runnable;
* 包含任务编号,每个任务执行时间为0.2s
*/
public class MyTask implements Runnable{
private int 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(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + name + " 完成了任务:" + id);
}
@Override
public String toString() {
return "MyTask{" +
"id=" + id +
'}';
}
}
MyWorker
package com.boot.bootdbbatis.excutor.demo1;
import java.util.List;
/**
* 编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
* 设计一个集合,用于保存所有的任务
*/
public class MyWorker extends Thread {
// 保存线程的名字
private String name;
private List<Runnable> tasks;
public MyWorker(String name, List<Runnable> tasks){
super(name);
}
@Override
public void run() {
// 判断集合中是否有任务,主要有,就一直执行
while(tasks.size() > 0){
Runnable r = tasks.remove(0);
r.run();
}
}
}
MyThreadPool
package com.boot.bootdbbatis.excutor.demo1;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* 这是自定义的线程池类
* 成员变量
* 1. 任务队列 集合 需要控制线程安全问题
* 2. 当前线程数量
* 3. 核心线程数量
* 4. 最大线程数量
* 5. 任务队列的长度
*
* 成员方法
* 1. 提交任务
* 将任务添加到任务集合中,需要判断是否超出了任务总长度
* 2. 执行任务
* 判断当前线程的数量,决定创建核心线程还是非核心线程
*/
public class MyThreadPool {
// 任务队列,存放Runnable
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
// 当前线程数量
private int num;
// 核心线程数量
private int corePoolSize;
// 最大线程数量
private int maxSize;
// 任务队列的长度
private int workSize;
public MyThreadPool(int corePoolSize, int maxSize, int workSize){
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
public void submit(Runnable r){
// 判断当前任务数量是否超出了最大任务数量
if(tasks.size() >= workSize){
System.out.println("任务:" + r + " 被丢弃了...");
}else{
tasks.add(r);
// 执行任务
execTask(r);
}
}
public void execTask(Runnable r){
// 判断当前线程池中的线程总数量,是否超出了核心数
if(num < corePoolSize){
// 相当于,小于核心线程数量的时候,使用线程直接执行任务
new MyWorker("核心线程:" + num, tasks).start();
num++;
}else if(num < maxSize){
new MyWorker("非核心线程:" + num, tasks).start();
num++;
}else{
System.out.println("任务:" + r + " 被缓存了...");
}
}
}
MyTest
package com.boot.bootdbbatis.excutor.demo1;
public class MyTest {
public static void main(String[] args) {
// 创建线程池类
MyThreadPool pool = new MyThreadPool(2, 4, 20);
// 提交多个任务
for(int i = 0; i < 30; i++){
MyTask my = new MyTask(i);
pool.submit(my);
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}