1.什么是线程池
线程池就是以一个或多个线程[循环执行]多个应用逻辑的线程集合.
1.1线程池的作用
- 线程池作用就是限制系统中执行线程的数量。
- 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。
- 用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。
- 若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
1.2线程池的实现
- 线程池定义的接口
public interface ThreadPools <Job extends Runnable>{
// 执行的这个任务job 必须实现Runnable 或者是runnable本身
/**
* 执行一个任务
* @param job
*/
public void execute(Job job);
/**
* 关闭线程池
*/
public void shutdown();
/**
* 增加工作者线程,用来执行任务的线程
* @param num
*/
public void addWorkers(int num);
/**
* 减少工作者线程
* @param num
*/
public void removeWorker(int num);
/**
* 获取正在执行任务数量
* @return
*/
public int getJobSize();
}
客户端可以通过execute(Job)方法将Job提交入线程池来执行,客户端完全不用等待Job的执行完成。除了execute(Job)方法以外,线程池接口提供了增加/减少工作者线程以及关闭线程池的方法。每个客户端提交的Job都会进入到一个工作队列中等待工作者线程的处理。
- 线程池默认实现
public class DefaultThreadPool<Job extends Runnable> implements ThreadPools<Job> {
/**
* 线程池维护线程工作者最大数量
*/
private static final int MAX_WORKER_NUMBERS = 30;
/**
* 线程池维护工作者线程的最默认工作数量
*/
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<Worker>());
/**
* 工作者线程的数量
*/
private int workerNum;
/**
* 每个工作者线程编号生成
*/
private final AtomicLong threadNum = new AtomicLong();
/**
* 第一步:构造函数,用于初始化线程池
* 首先判断初始化线程池的线程个数是否大于最大线程数,如果大于则线程池的默认初始化值为 DEFAULT_WORKER_NUMBERS
*/
public DefaultThreadPool(int num) {
if (num > MAX_WORKER_NUMBERS) {
this.workerNum = DEFAULT_WORKER_NUMBERS;
} else {
this.workerNum = num;
}
initializeWorkers(workerNum);
}
/**
* 初始化每个工作者线程
* @param workerNum 线程个数
*/
private void initializeWorkers(int workerNum) {
for (int i = 0; i < workerNum; i++) {
Worker worker = new Worker();
workers.add(worker);
// 启动工作线程
Thread thread = new Thread(worker);
thread.start();
}
}
@Override
public void execute(Job job) {
if (job == null) {
throw new NullPointerException();
}
// TODO 当Job多于Workers个数的时候,考虑如何临时添加线程数
synchronized (jobs) {
// 根据线程的"等待/通知机制"这里必须对jobs加锁
jobs.add(job);
jobs.notify();
}
}
@Override
public void shutdown() {
// 让线程自己关闭--然后线程池就自己关闭了。
for (Worker worker : workers) {
worker.shutdown();
}
}
/**
* 添加工作线程,用来执行任务
*
* @param num 又添加线程的个数
*/
@Override
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 减少线程的个数---这里不应该随意移除,应该移除哪些没有工作的线程。
* 或者等全部线程空闲时候然后进行统一移除。
*/
@Override
public void removeWorker(int num) {
synchronized (jobs) {
if (num >= this.workerNum) {
throw new IllegalArgumentException("超过已有的线程数量");
}
// 倒序移除,防止越界
for (int i = num -1; i >= 0; i--) {
Worker worker = workers.get(i);
if (worker != null) {
worker.shutdown();
workers.remove(i);
}
}
this.workerNum -= num;
}
}
/**
* 正在执行的线任务数量
*
* @return 应该是任务队列中长度
*/
@Override
public int getJobSize() {
return jobs.size();
}
// 真正的工作线程,处理job任务。
private class Worker implements Runnable {
/**
* 表示该线程是否允许
*/
private volatile boolean running = true;
@Override
public void run() {
while (running) {
Job job = null;
// 线程的等待/通知机制
synchronized (jobs) {
// 说明还没有任务---
if (jobs.isEmpty()) {
try {
jobs.wait(); // 线程等待唤醒
} catch (InterruptedException e) {
// 感知外部对该线程的终端操作,返回
Thread.currentThread().interrupt();
return;
}
}
// 得到第一个任务
job = jobs.removeFirst();
}
if (job != null) {
// 最终还是任务执行
job.run();
}
}
}
// 关闭该线程
public void shutdown() {
running = false;
}
}
}
从线程池的实现中可以看出,当客户端调用execute(Job)方法时,会不断地向任务列表jobs中添加Job,而每个工作者线程会不读的从jobs上获取Job来执行,当jobs为空时,工作者线程进入WAITING状态。
当添加一个Job后,对工作队列jobs调用其notify()方法来唤醒一个工作者线程。此处我们不调用notifyAll(),避免将等待队列中的线程全部移动到阻塞队列中而造成资源浪费。
- Job实现
package jvm.juc.pools;
import java.util.concurrent.atomic.AtomicInteger;
public class Job implements Runnable{
private final AtomicInteger number;
public Job(int number) {
this.number = new AtomicInteger(number);
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名称: " + Thread.currentThread().getName() + "; job "+ number.get() +"被执行了");
}
}
- 测试程序
package jvm.juc.pools;
public class WorkTest {
public static void main(String[] args) {
DefaultThreadPool<Job> defaultThreadPool = new DefaultThreadPool<Job>(10);
for (int i = 0; i < 100; i++) {
Job job = new Job(i);
defaultThreadPool.execute(job);
}
}
}
以上代码部分结果展示,可以看出这100个Job全部被消耗了。而且运行时长也在十几秒左右,提升了效率。
2.总结
线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端线程。客户端线程把任务放入工作队列后便返回,而工作者线程则不端的从工作队列中取出工作并执行。当工作队列为空时,工作者线程进入WAITING状态,当有客户端发送任务过来后会通过任意一个工作者线程,随着大量任务的提交,更多的工作者线程被唤醒。