一、为什么要有线程池?
启动线程去做任务可以发挥多核CPU的优势,提高程序执行性能。但频繁的创建、销毁线程对象又会导致整体系统的执行效率不高,甚至出现严重问题。所以要引入“池化”概念。预先准备一些资源在池子中,有需要时就从池子中获取资源;如果资源已经被用光,再有请求过来则需要等待资源被释放后才能使用空闲的资源。达到重复利用,提高整体性能。
二、线程池做了什么?
线程池核心工作过程:
1、初始化线程池,指定线程池的大小
2、向线程池中放入任务执行
3、如果线程池中创建的线程数目未达到指定大小,则创建我们自定义的线程类放入线程池集合,并立刻执行任务。执行完毕后该线程会一直监听队列
4、如果线程池中创建的线程数目已满,则将任务放入阻塞缓存队列中(阻塞队列保证队列的执行顺序,不会有并发问题)
5、线程池中所有创建的线程,都会一直从缓存队列中取任务,取到任务立马执行
三、如何自行实现一个线程池?
public class CustomThreadPool {
// 存放线程的集合
private ArrayList<CustomThread> threads;
// 任务队列
private ArrayBlockingQueue<Runnable> taskQueue;
// 线程初始化限定大小
private int threadNum;
// 已经工作的线程数目
private int workThreadNum;
private final ReentrantLock mainLock = new ReentrantLock();
public CustomThreadPool(int initPoolNum) {
threadNum = initPoolNum;
threads = new ArrayList<>(initPoolNum);
// 任务队列初始化为线程池线程数的四倍
taskQueue = new ArrayBlockingQueue<Runnable>(initPoolNum * 4);
workThreadNum = 0;
}
public void execute(Runnable runnable) {
try {
mainLock.lock();
// 线程池未满,每加入一个任务开启一个线程
if (workThreadNum < threadNum) {
CustomThread customThread = new CustomThread(runnable);
customThread.start();
threads.add(customThread);
workThreadNum++;
} else { // 线程池已满,放入任务队列,等待空闲线程执行
// 队列已满,无法再添加任务,则会拒绝这个任务
// offer 的作用,在不超出队列长度的情况下在队列尾部插入元素,如果成功则返回true,如果失败则返回false
if (!taskQueue.offer(runnable)) {
rejectTask();
}
}
} finally {
mainLock.unlock();
}
}
private void rejectTask() {
System.out.println("任务队列已满,无法再添加,请扩大你的初始线程数量");
}
class CustomThread extends Thread {
private Runnable task;
public CustomThread(Runnable runnable) {
this.task = runnable;
}
@Override
public void run() {
super.run();
// 该线程一直启动,不断从任务队列中取出任务执行
while (true) {
// 如果初始化任务不为空,则直接执行初始化任务
if (task != null) {
task.run();
task = null;
} else { // 如果没有初始化任务,则从任务队列中获取任务并执行
Runnable queueTask = taskQueue.poll();
if (queueTask != null) {
queueTask.run();
}
}
}
}
}
}
测试过程
CustomThreadPool pool = new CustomThreadPool(5);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行中");
}
};
for (int i = 0; i < 20; i++) {
pool.execute(task);
}
简单总结:
在不断添加任务的过程中,会优先启动核心线程,直到核心线程数达到5,这5个核心线程会一直在后面运行,处理完自己携带的任务后,就会继续从阻塞队列中获取缓存任务,由于是阻塞队列,一个线程在获取任务的过程中,其他线程会等待这个线程获取成功后再获取任务。获取完任务后就会执行,然后线程进行下一次的轮询继续获取任务并进行执行。说明这5条核心线程对象是会一直存在,直到系统退出。
网友评论