什么是线程池?为什么需要线程池?
客户端得任务小而杂,一个任务创建一个线程,对于服务器来说,太多,如果有成千上万的任务,创建成千上万给线程,很不划算,线程多了引起的上下文切换也会变多,上下文切换是影响效率的。
线程池技术能够很好地解决这个问题,它预先创建了若干数量的线程,并且不能由用户直接对线程的创建进行控制,在这个前提下重复使用固定或较为固定数目的线程来完成任务的执行。这样做的好处是,一方面,消除了频繁创建和消亡线程的系统资源开销,另一方面,面对过量任务的提交能够平缓的劣化。
简单的线程池接口定义
public interface ThreadPool <Job extends Runnable>{
//执行一个job,这个job需要实现runnable
void execute(Job job);
//关闭线程池
void shutdown();
//增加工作者线程
void addWorkers(int num);
//减少工作者线程
void removeWorker(int num);
int getJobSize();
//得到正在等待执行得任务数量
}
客户端可以通过execute(Job)方法将Job提交入线程池执行,而客户端自身不用等待Job的执行完成。除了execute(Job)方法以外,线程池接口提供了增大/减少工作者线程以及关闭线程池的方法。这里工作者线程代表着一个重复执行Job的线程,而每个由客户端提交的Job都将进入到一个工作队列中等待工作者线程的处理。
线程池的实现
/**
* 默认线程池实现
* @param <Job> 线程池中的任务类型,必须实现 Runnable 接口
*/
public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job> {
// 线程池最大工作线程数
private static final int MAX_WORKER_NUMBERS = 10;
// 线程池默认工作线程数
private static final int DEFAULT_WORKER_NUMBERS = 5;
// 线程池最小工作线程数
private static final int MIN_WORKER_NUMBERS = 1;
// 任务队列,存放待处理的任务
private final LinkedList<Job> jobs = new LinkedList<>();
// 工作线程列表,使用线程安全的列表存放工作线程
private final List<Worker> workers = Collections.synchronizedList(new ArrayList<>());
// 当前工作线程数
private int workerNum = DEFAULT_WORKER_NUMBERS;
// 线程编号生成器
private AtomicLong threadNum = new AtomicLong();
/**
* 默认构造函数,初始化时使用默认线程数量
*/
public DefaultThreadPool() {
initializeWorkers(DEFAULT_WORKER_NUMBERS);
}
/**
* 构造函数,允许指定线程池的工作线程数量
* @param num 指定的线程数量
*/
public DefaultThreadPool(int num) {
// 确保工作线程数量在最小值和最大值之间
workerNum = num > MAX_WORKER_NUMBERS ? MAX_WORKER_NUMBERS : (num < MIN_WORKER_NUMBERS ? MIN_WORKER_NUMBERS : num);
initializeWorkers(workerNum);
}
/**
* 提交一个任务到线程池
* @param job 要提交的任务
*/
public void execute(Job job) {
if (job != null) {
synchronized (jobs) {
// 将任务添加到任务队列
jobs.addLast(job);
// 唤醒等待的线程
jobs.notify();
}
}
}
/**
* 关闭线程池,停止所有工作线程
*/
public void shutdown() {
for (Worker worker : workers) {
worker.shutdown();
}
}
/**
* 增加工作线程
* @param num 要增加的线程数量
*/
public void addWorkers(int num) {
synchronized (jobs) {
// 如果增加的线程数超出最大限制,则调整为最大限制
if (num + this.workerNum > MAX_WORKER_NUMBERS) {
num = MAX_WORKER_NUMBERS - this.workerNum;
}
// 初始化新增的工作线程
initializeWorkers(num);
// 更新工作线程数
this.workerNum += num;
}
}
/**
* 移除工作线程
* @param num 要移除的线程数量
*/
public void removeWorker(int num) {
synchronized (jobs) {
// 如果要移除的线程数超过当前工作线程数,抛出异常
if (num >= this.workerNum) {
throw new IllegalArgumentException("超出工作线程数");
}
int count = 0;
while (count < num) {
Worker worker = workers.get(count);
if (workers.remove(worker)) {
worker.shutdown();
count++;
}
}
// 更新工作线程数
this.workerNum -= count;
}
}
/**
* 获取当前待处理任务的数量
* @return 待处理任务的数量
*/
public int getJobSize() {
return jobs.size();
}
/**
* 初始化工作线程
* @param num 要初始化的线程数量
*/
private void initializeWorkers(int num) {
for (int i = 0; i < num; i++) {
Worker worker = new Worker();
workers.add(worker);
Thread thread = new Thread(worker, "ThreadPool-Worker-" + threadNum.incrementAndGet());
thread.start();
}
}
/**
* 工作线程类,负责执行任务
*/
class Worker implements Runnable {
// 标记工作线程是否运行
private volatile boolean running = true;
@Override
public void run() {
while (running) {
Job job = null;
synchronized (jobs) {
while (jobs.isEmpty()) {
try {
// 如果任务队列为空,线程进入等待状态
jobs.wait();
} catch (InterruptedException ex) {
// 捕获并处理线程中断异常
Thread.currentThread().interrupt();
return;
}
}
// 从任务队列中取出任务
job = jobs.removeFirst();
}
if (job != null) {
try {
// 执行任务
job.run();
} catch (Exception e) {
// 捕获任务执行中的异常,但不处理
e.printStackTrace();
}
}
}
}
/**
* 关闭工作线程
*/
public void shutdown() {
running = false;
}
}
}
-
类和字段:
DefaultThreadPool
类实现了一个简单的线程池,内部使用了jobs
列表来存储任务,workers
列表来存储工作线程。- 常量
MAX_WORKER_NUMBERS
、DEFAULT_WORKER_NUMBERS
和MIN_WORKER_NUMBERS
分别定义了线程池的最大、默认和最小线程数。
-
构造函数:
- 默认构造函数和带参数的构造函数分别用于初始化线程池,后者允许指定线程池的初始线程数量,并进行合理的限制。
-
方法:
execute(Job job)
用于提交任务到线程池中。shutdown()
用于关闭线程池,停止所有工作线程。addWorkers(int num)
用于增加工作线程的数量,并处理线程数的限制。removeWorker(int num)
用于减少工作线程的数量,并处理线程数的限制。getJobSize()
返回当前待处理任务的数量。
-
内部类
Worker
:Worker
实现了Runnable
接口,负责从任务队列中获取任务并执行。run()
方法中,如果任务队列为空,线程会进入等待状态;有任务时则执行任务。shutdown()
方法用于停止工作线程的运行。