手撕线程池 ThreadPool
为了更加方便理解线程池
我们都知道线程池和任务就好比生产者消费者之间的关系也就是如图!
先分析:我们可以将线程池中的线程看作消费者,可以将main(只是方便测试)看作生产者
总体思路:
实现一个阻塞队列,因为阻塞,阻塞队列是连接他们的桥梁,我们线程池中的线程去队列中获取任务执行,而我们main,提供任务到阻塞队列中供线程池执行!其次我们需要实现ThreadPool,我们可以在主线程当作消费者!
第一步:分析阻塞队列
- 我们阻塞队列当中应当包含一个任务队列,来记录我们待执行的任务
- 还应该给任务队列的对头元素加锁,这里使用ReentrantLock,加锁的目的,避免我们线程池中多个线程同时或许一个任务,保证线程安全 ;
- 同样我们的任务队列需要一个容量capacity ;
- 还需要两个条件变量Condition,(1)如果任务队列为空,线程池中的线程去休息,(2)如果任务队列满了,超出容量的任务需要去等待!
第二步: 分析线程池
- 我们的线程池需要关联阻塞队列
- 我们的线程池需要保存多个线程类,可以用HashSet ;
- 我们的线程池因该具有核心线程数 coreSize ;
- 而且我们的线程池需要设置任务的超时时间,线程池中的线程会等待任务队列当中的任务,一旦超时线程池中的线程就不会再等待!
- 在为时间设置一个时间单位!
- 还需要一个成员内部类wordk,封装一下我们的Thread
第三步:将线程池和阻塞队列中的方法实现
阻塞队列中提供的的方法
- 线程池中的线程线程(消费者)获取当前队列中的任务的方法
- 生产者向任务队列当中提供任务
- 获取任务队列的大长度
线程池中的方法
- 首当其冲的就是要有一个执行任务的方法 excute(),(用于接收任务)
- 其次就是我们线程池中线程执行的run方法,执行我们的具体任务!(拿到任务,用于具体执行)
到这,我们手写线程池的东西都理清了代码如下
package com.pool;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/*
* 手写线程池ThreadPool
*/
public class TestPool {
public static void main(String[] args) {
//创建线程池对象
ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10);
//模拟提供5个任务放给线程池,然而我们线程池设置2个核心线程!
for (int i = 0; i < 5; i++) {
int j = i ;
threadPool.execute(()->{
System.out.println("任务:"+ j);
});
}
}
}
/*
* 线程池
*/
class ThreadPool{
//任务队列
private BlockQueue<Runnable> taskQueue ; //可以讲任务抽象为Runnable(可运行的!)
//县线程集合
private HashSet<Worker> workers = new HashSet() ;
//核心线程数
private int coreSize ;
//获取任务的超时时间
private long timeout ;
//时间单位
private TimeUnit timeUnit ;
public void execute (Runnable task){
//加锁保障workers的线程安全
synchronized (workers){
//如果我们的任务数 < coreSize , 直接创建空闲线程,执行!
if (workers.size() < coreSize){
Worker worker = new Worker(task);
System.out.println("新增worker :" + worker);
worker.start(); //线程执行执行任务
workers.add(worker) ;
} else { //如果我们的任务数 > coreSize , 直接加入任务队列
taskQueue.put(task);
System.out.println("加入任务队列 :" + task);
}
}
}
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit,int queueCapacity) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockQueue<>(queueCapacity);
}
//将线程池中的线程包装为一个work类
class Worker extends Thread{
private Runnable task ;
public Worker(Runnable task) {
this.task = task;
}
@Override //执行任务
public void run() {
//task不为空,直接执行任务
//当task不为空任务队列有任务执行,队列中的任务
// while(task != null || (task = taskQueue.take()) != null) //死等,线程池中的线程一直等待获取任务队列中的任务
while(task != null || (task = taskQueue.poll(timeout,timeUnit)) != null){ //等一会儿任务队列没有就不等了,停止线程,下次啥时候来,啥时候再新建
try{
System.out.println("正在运行任务:" + task);
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null ;
}
}
synchronized (workers){
System.out.println("线程执行任务结束,移处线程池 :" + this);
workers.remove(this) ;
}
}
}
}
/*
* 阻塞队列
*/
class BlockQueue<T>{
//1、任务队列
private Deque<T> queue = new ArrayDeque<>() ;
//2、锁 : 用于锁住对头元素,防止其被多个线程同时获取!
private ReentrantLock lock = new ReentrantLock() ;
//3、生产者条件变量 (1) 任务队列满了,任务(生产者)就会进入WaitSet
private Condition fullWaitSet = lock.newCondition() ;
//4、消费者条件变量 (2) 任务队列为空,线程池中的线程(消费者)进WaitSet
private Condition emptyWaitSet = lock.newCondition() ;
//5、容量
private int capacity ;
public BlockQueue(int capacity) {
this.capacity = capacity;
}
//增强我们线程获取当前队列中的任务的方法,如果队列为空,进入休息室等待(带时限的,不会死等)
public T poll(long timeout , TimeUnit unit){
lock.lock();
try{
long nanos = unit.toNanos(timeout) ; //超时时间统一转换为纳秒
while(queue.isEmpty()){ //队列为空,线程池中的线程去等待
try {
if(nanos <= 0) return null ; //如果nanos <= 0 说明超时限了,直接唤醒
nanos = emptyWaitSet.awaitNanos(nanos); //如果被提前唤醒,返回nanos的剩余时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T task = queue.removeFirst();//队列不为空,获取并且移除队头任务
fullWaitSet.signal(); //有空闲线程,唤醒正在休息的任务
return task ; //返回取到的任务
}
finally {
lock.unlock();
}
}
//线程(消费者)获取任务队列当中的任务
public T take(){
lock.lock();
try{
while(queue.isEmpty()){ //队列为空,线程池中的线程去等待
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T task = queue.removeFirst();//队列不为空,获取并且移除队头任务
fullWaitSet.signal(); //有空闲线程,唤醒正在休息的任务
return task ;
}
finally {
lock.unlock();
}
}
//生产者向任务队列当中提供任务
public void put(T element){
lock.lock();
try{
while(queue.size() == capacity){ //任务队列已经满了,新任务去等待
try {
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element); //任务队列不满,添加任务到末尾
emptyWaitSet.signal(); //队列中添加一个任务,唤醒一个线程池中正在休息的线程
}finally {
lock.unlock();
}
}
//获取任务队列的大小
public int size(){
lock.lock();
try{
return queue.size(); //返回任务队列长度即可
}finally {
}
}
}
测试结果 :
发现与预期结果一致!