线程池是开发中常用的工具,要想掌握线程池,最好的方法就是自己手动实现一个。
任务类
public class Task {
private int id;
private Runnable job;
public Task(Runnable job) {
this.job = job;
}
public Task(int id, Runnable job) {
this.id = id;
this.job = job;
}
public void job() {
job.run();
}
public int getid() {
return id;
}
}
线程池类
public class ThreadPoolExecutor {
//线程池的容量
private int poolSize = 0;
//核心线程的数量
private int coreSize = 0;
//阻塞队列
private BlockingQueue<Task> blockingQueue;
//是否关闭线程池,用 volatile 保证可见性,确保线程可以及时关闭
private volatile boolean shutdown = false;
/**
* @title: ThreadPoolExecutor
* @description: 构造方法
* @param: @param size 线程池容量
*/
public ThreadPoolExecutor(int poolsize) {
this.poolSize = poolsize;
blockingQueue = new LinkedBlockingQueue<Task>();
}
/**
* @title: execute
* @description: 添加任务
* @author: JerryG
* @param task 要添加的任务
* @throws InterruptedException
*/
public void execute(Task task) throws InterruptedException {
//判断线程池是否关闭
if(shutdown == true) {
return;
}
//判空
if(task == null) {
throw new NullPointerException("ERROR:传入的task为空!");
}
if(coreSize < poolSize) {
//如果核心线程数小于线程池容量,将任务加入队列并新建核心线程
blockingQueue.put(task);
addWorker(task);
}else {
//否则,只将任务加入队列
blockingQueue.put(task);
}
}
/**
*
* @title: addWorker
* @description: 添加真正用于执行任务的线程
* @author: JerryG
* @date: 2019年8月22日 下午2:28:31
* @param task
* @throws:
*/
public void addWorker(Task task) {
Thread thread = new Thread(new Worker());
thread.start();
coreSize ++;
}
/**
*
* @title: showdown
* @description: 停止线程池
* @author: JerryG
*/
public void showdown() {
shutdown = true;
}
/**
* @description:具体进行工作的线程
* @author:JerryG
*/
class Worker implements Runnable{
@Override
public void run() {
while(!shutdown) {
try {
//循环从队列中取出任务并执行
Task task = blockingQueue.take();
task.job();
System.out.println("taskid = " + task.getid() + " 执行完毕" );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
关于队列的选择
之所以选择 LinkedBlockingQueue 原因如下:
- LinkedBlockingQueue 底层是基于链表的,如果不指定容量,其最大存储容量将是Integer.MAX_VALUE,几乎可以认为是一个“无界”的队列,由于其节点的创建都是动态创建,并且在节点出队列后可以被GC所回收,因此其具有灵活的伸缩性。任务多的情况下,如果使用一个有界的阻塞队列(例如ArrayBlockingQueue)来进行处理,那么就非常有可能很快导致队列满的情况发生。
- LinkedBlockingQueue的读取和插入操作所使用的锁是两个不同的lock,它们之间的操作互相不受干扰,因此两种操作可以并行完成,因此其吞吐量较高;