线程池原理:手写简单线程池
一、线程池作用
- 1.提高程序执行效率,并发执行提高响应速度;
- 2.线程复用,减少频繁创建销毁线程对服务器的性能消耗
- 3.有利于对线程进行管理
二、线程池基本的常见构成要素
- 1.执行的线程数量,开启执行的线程;
- 2.执行的任务集合
- 3.构造方法
- 4.executor方法(有返回值)或submit方法(无返回值)
- 5.shutdown停止线程的方法
参考简图:
三、手写简单线程池样例,有利于加深对线程池工作原理的理解
package com.demo.spring.test.threadPool;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @Description: 简单模拟手写线程池,方便理解线程池工作原理
* @Author: 狗狗大蛇
* @Date:
*/
public class MyThreadPoolExecutor {
// 工作线程集合
private List<TaskThread> threads;
// 阻塞队列:executor(Runnable 实参)传进来的工作任务均先存放到该队列中,等待TaskThread去取出来执行;
private BlockingQueue<Runnable> workQueue;
// 控制线程池停止的标记
private volatile Boolean shutDownFlag = true;
/**
* 线程池构造方法
* @param threadNum : 初始创建保持运行状态线程的数量
* @param works : 线程池处理的工作任务数量
*/
public MyThreadPoolExecutor(int threadNum,int works){
// 创建指定大小的队列容器,用于存放任务;
this.workQueue = new LinkedBlockingQueue<>(works);
// 创建threadNum运行状态的线程数量,并进入就绪状态
this.threads = new ArrayList<>();
for(int i = 0 ; i < threadNum; i++){
TaskThread workThread = new TaskThread();
Thread thread = new Thread(workThread);
thread.start();
this.threads.add(workThread);
}
}
/**
* 定义一个工作线程:也叫服务线程:指不断获取队列中的消息来进行处理的线程;
* 1.在线程池中始终保持运行状态;
* 2.不停的去工作任务队列中去取任务;
*/
class TaskThread implements Runnable{
@Override
public void run() {
// 1.如果要线程保持运行状态,需要写死循环
while (shutDownFlag){
// 2.队列中取任务执行,取完删掉对应任务;
Runnable task = null;
try {
// 此处不用poll,poll会不停的取,导致CPU使用率居高不下;用take没有任务以后会等待,CPU使用率消耗特别低;
task = workQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(null != task){
task.run();
}
}
}
}
/**
* 线程池启动执行的方法
* @param runnable 放入需要执行的业务任务线程;
* @return
*/
public void executor(Runnable runnable){
// 将业务线程放到队列容器中,运行中的线程会不停的到队列中取任务;
// offer(): 非阻塞式添加任务
try {
workQueue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void shutdown(){
this.shutDownFlag = false;
}
// 测试效果
public static void main(String[] args) throws InterruptedException {
MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(4,8);
SendEmailTask sendEmailTask = new SendEmailTask();
boolean flag = false;
for(int i = 0; i < 100; i++){
myThreadPoolExecutor.executor(sendEmailTask);
if( i == 99 ){
flag = true;
}
}
// 模拟等待10秒后又有新的任务调用线程
Thread.sleep(10000);
System.out.println("模拟等待10秒");
myThreadPoolExecutor.executor(sendEmailTask);
System.out.println("结束");
// 模拟等待10秒没有任务,停止线程
Thread.sleep(10000);
if(flag){
myThreadPoolExecutor.shutdown();
}
}
}
/**
* @Description: 具体发送邮件的业务线程
* @Author: yangshilei
* @Date:
*/
class SendEmailTask implements Runnable{
@Override
public void run() {
try {
// 模拟实际业务处理占用的时间
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName() + ":" + "短信发送成功");
}
}
四、执行结果
我们可以看到已经实现了线程的复用:
且始终只有0,1,2,3这4开启的线程;
线程:Thread-3:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-0:短信发送成功
线程:Thread-1:短信发送成功
线程:Thread-2:短信发送成功
线程:Thread-3:短信发送成功
模拟等待10秒
结束
线程:Thread-0:短信发送成功
五、测试过程中发现CPU占用过高的问题以及处理方法
在使用poll()方法获取任务,导致CPU占用率很高,如下截图:
处理办法,将poll()换成take()进行获取任务,没有任务时候等待任务;测试代码中最后部分就是测试,等待后有新任务是否会生效的测试,如下代码和截图:
// 模拟等待10秒后又有新的任务调用线程
Thread.sleep(10000);
System.out.println("模拟等待10秒");
myThreadPoolExecutor.executor(sendEmailTask);
System.out.println("结束");