并发编程——手写一个简单的线程池

35 阅读7分钟

*/

private static class SkylinePool {

//线程池的线程数

private final int coreSize;

//线程池中的线程的集合

private final TaskHandler[] threads;

//线程池中任务的等待队列

private final TaskQueue taskQueue;

//指针,指向threads的末尾

private int endPoint;

//是否已停止

private boolean shutDown;

//拒绝策略

private final RejectStrategy rejectStrategy;

/**

  • 构造函数

  • @param coreSize 线程池中的线程个数

  • @param maxQueueSize 等待队列中的元素的最大个数

  • @param rejectStrategy 当等待队列已满时需要执行的逻辑

*/

private SkylinePool(int coreSize,int maxQueueSize,RejectStrategy rejectStrategy) {

this.coreSize = coreSize;

this.threads = new TaskHandler[coreSize];

taskQueue = new TaskQueue(maxQueueSize);

this.rejectStrategy = rejectStrategy;

}

/**

  • 向线程池中提交一个任务

  • @param task

*/

public void submit(Task task) {

//如果线程池已经停止,那就不再允许提交了

if (shutDown) {

return;

}

//如果线程数还没满,那就先创建线程

if (endPoint < coreSize) {

logger.info("线程池还没到最大限度,任务[{}]被直接启动执行",task.getTaskNum());

TaskHandler taskHandler = new TaskHandler(task, taskQueue, "t"+endPoint);

threads[endPoint] = taskHandler;

taskHandler.start();

endPoint++;

} else {

//线程数已经满了之后就只能尝试放入队列了,这个try就有aqs那味儿了

logger.info("线程池已到达最大限度,任务[{}]尝试进入队列",task.getTaskNum());

boolean tryPush = taskQueue.tryPush(task);

//尝试放入队列失败,一般就是队列满了,那就执行拒绝策略

if (!tryPush) {

rejectStrategy.handle(taskQueue, task);

}

}

}

public void shutDown(){

shutDown = true;

for (int i = 0; i < coreSize; i++) {

TaskHandler thread = threads[i];

if (thread == null) {

continue;

}

while (thread.isAlive() && !thread.isInterrupted()){

logger.info("尝试打断[{}]",thread.getName());

thread.interrupt();

try {

//等待1秒是为了让出CPU

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

threads[i] = null;

}

}

}

/**

  • 任务队列,尝试写了一个双端同步FIFO队列

*/

private static class TaskQueue{

//队列中元素的最大值

private final int maxSize;

private final Task[] taskDeque;

//头索引

private int headIndex;

//尾索引

private int tailIndex;

//同步锁

private final Lock lock = new ReentrantLock();

//条件锁,当队列为空时会阻塞

private final Condition emptyCondition = lock.newCondition();

//条件锁,当队列已满时会阻塞

private final Condition fullCondition = lock.newCondition();

/**

  • 初始化一个任务队列

  • @param maxSize

*/

private TaskQueue(int maxSize) {

if (maxSize<=0) {

throw new RuntimeException("maxSize不能小于0");

}

this.maxSize = maxSize;

//初始化队列

taskDeque = new Task[maxSize];

}

/**

  • 从任务队列中取出一个task,如果没有,就阻塞

  • @return

*/

public Task take(){

//如果当前线程已经被打断,那就直接返回null

if (Thread.currentThread().isInterrupted()) {

logger.info("[{}]已经被打断",Thread.currentThread().getName());

return null;

}

//抢锁,准备取出一个任务

logger.info("[{}]开始获取任务",Thread.currentThread().getName());

lock.lock();

try {

//如果队列为空,那就阻塞,这里需要是while而不是if,因为下面调用的是signalAll,而且也可以防止意外唤醒

while (checkIsEmpty()){

logger.info("队列为空,等待其他线程放入任务后,才能取出任务,[{}]",Thread.currentThread().getName());

try {

emptyCondition.await();

} catch (InterruptedException e) {

logger.info("[{}]已经被打断",Thread.currentThread().getName());

Thread.currentThread().interrupt();

return null;

}

}

logger.info("取出了一个任务,[{}]",Thread.currentThread().getName());

Task task = doPop();

//取出一个任务后,列表肯定不是full的状态了

fullCondition.signalAll();

return task;

}finally {

lock.unlock();

}

}

/**

  • 从列表中取出一个任务

  • @return

*/

public Task doPop(){

Task pop = taskDeque[headIndex];

taskDeque[headIndex] = null;

headIndex++;

headIndex = headIndex%maxSize;

return pop;

}

/**

  • 判断数组是否已经满了

  • 1、head...tail

  • 2、...tail,head...

  • @return

*/

private boolean checkIsFull(){

int i = tailIndex - headIndex;

return i == maxSize-1 || i == -1;

}

/**

  • 判断数组是否为空的

  • @return

*/

private boolean checkIsEmpty(){

return tailIndex==headIndex;

}

/**

  • 尝试放入一个任务

  • @param task

  • @return

*/

public boolean tryPush(Task task){

//抢锁,尝试放入

logger.info("[{}]开始尝试放入任务[{}]",Thread.currentThread().getName(),task.getTaskNum());

lock.lock();

try {

//如果已经满了,那就直接返回,aqs风格

if (checkIsFull()){

return false;

}

//队列中有空闲位置的话,那就放入任务

logger.info("任务[{}]成功进入队列,[{}]",task.getTaskNum(),Thread.currentThread().getName());

doAdd(task);

//放入之后,列表一定不为空了

emptyCondition.signalAll();

return true;

}finally {

lock.unlock();

}

}

/**

  • 放入一个任务

  • @param task

*/

public void doAdd(Task task){

taskDeque[tailIndex]=task;

tailIndex++;

tailIndex=tailIndex%maxSize;

}

/**

  • 向队列中添加一个任务,如果队列到达最大长度,那就阻塞

  • @param task

*/

public void push(Task task){

logger.info("[{}]开始放入任务[{}]",Thread.currentThread().getName(),task.getTaskNum());

lock.lock();

try {

while (checkIsFull()){

logger.info("队列已满,等待任务被取出后,才能继续,[{}]",Thread.currentThread().getName());

try {

fullCondition.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

logger.info("任务[{}]成功进入队列,[{}]",task.getTaskNum(),Thread.currentThread().getName());

doAdd(task);

emptyCondition.signalAll();

}finally {

lock.unlock();

}

}

}

/**

  • 任务处理器,其实就是线程

*/

private static class TaskHandler extends Thread{

//当前要处理的任务

private Task task;

//待处理任务的队列

private final TaskQueue taskQueue;

/**

  • 任务处理器

  • @param task 初始时的任务

  • @param taskQueue 待处理任务的队列

  • @param name 任务处理器名称(线程名)

*/

public TaskHandler(Task task, TaskQueue taskQueue, String name){

this.task = task;

this.taskQueue = taskQueue;

setName(name);

}

@Override

public void run() {

//如果有当前任务或者队列中可以获取到任务

while (task!=null || (task=taskQueue.take())!=null){

logger.info("[{}]开始执行任务[{}]",Thread.currentThread().getName(),task.getTaskNum());

task.run();

if (Thread.currentThread().isInterrupted()) {

logger.info("[{}]已经被打断",Thread.currentThread().getName());

return;

}

//将当前任务置为null

task = null;

}

logger.info("[{}]no task",Thread.currentThread().getName());

}

}

/**

  • 任务,基于Runnable

*/

private static abstract class Task implements Runnable{

//任务编号

private final int taskNum;

/**

  • 任务

  • @param taskNum 任务编号

*/

private Task(int taskNum) {

this.taskNum = taskNum;

}

public int getTaskNum() {

return taskNum;

}

}

/**

  • 拒绝策略

*/

private interface RejectStrategy{

/**

  • 当队列已满时会触发的具体逻辑

  • @param taskQueue

  • @param task

*/

void handle(TaskQueue taskQueue,Task task);

}

执行结果:

11:03:42.935 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池还没到最大限度,任务[0]被直接启动执行

11:03:42.936 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池还没到最大限度,任务[1]被直接启动执行

11:03:42.937 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[2]尝试进入队列

11:03:42.937 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[2]

11:03:42.937 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]开始执行任务[0]

11:03:42.937 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]开始执行任务[1]

11:03:42.937 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[2]成功进入队列,[Test worker]

11:03:42.937 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]正在执行任务[0]

11:03:42.937 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[3]尝试进入队列

11:03:42.937 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[3]

11:03:42.937 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[3]成功进入队列,[Test worker]

11:03:42.937 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]正在执行任务[1]

11:03:42.937 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[4]尝试进入队列

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[4]

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[4]成功进入队列,[Test worker]

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[5]尝试进入队列

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[5]

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[5]成功进入队列,[Test worker]

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[6]尝试进入队列

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[6]

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - -----------[Test worker]尝试放入任务[6]

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始放入任务[6]

11:03:42.938 [Test worker] INFO c.e.d.DemoApplicationTests9 - 队列已满,等待任务被取出后,才能继续,[Test worker]

11:03:44.950 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]执行任务[1]完毕

11:03:44.950 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]执行任务[0]完毕

11:03:44.950 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]开始获取任务

11:03:44.950 [t1 ] INFO c.e.d.DemoApplicationTests9 - 取出了一个任务,[t1]

11:03:44.950 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]开始获取任务

11:03:44.950 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]开始执行任务[2]

11:03:44.950 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[6]成功进入队列,[Test worker]

11:03:44.950 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]正在执行任务[2]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - ------------[Test worker]放入任务[6]成功

11:03:44.951 [t0 ] INFO c.e.d.DemoApplicationTests9 - 取出了一个任务,[t0]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[7]尝试进入队列

11:03:44.951 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]开始执行任务[3]

11:03:44.951 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]正在执行任务[3]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[7]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[7]成功进入队列,[Test worker]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[8]尝试进入队列

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[8]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - -----------[Test worker]尝试放入任务[8]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始放入任务[8]

11:03:44.951 [Test worker] INFO c.e.d.DemoApplicationTests9 - 队列已满,等待任务被取出后,才能继续,[Test worker]

11:03:46.965 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]执行任务[3]完毕

11:03:46.965 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]开始获取任务

11:03:46.965 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]执行任务[2]完毕

11:03:46.965 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]开始获取任务

11:03:46.965 [t0 ] INFO c.e.d.DemoApplicationTests9 - 取出了一个任务,[t0]

11:03:46.965 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]开始执行任务[4]

11:03:46.965 [t1 ] INFO c.e.d.DemoApplicationTests9 - 取出了一个任务,[t1]

11:03:46.965 [t0 ] INFO c.e.d.DemoApplicationTests9 - [t0]正在执行任务[4]

11:03:46.965 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]开始执行任务[5]

11:03:46.965 [t1 ] INFO c.e.d.DemoApplicationTests9 - [t1]正在执行任务[5]

11:03:46.965 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[8]成功进入队列,[Test worker]

11:03:46.965 [Test worker] INFO c.e.d.DemoApplicationTests9 - ------------[Test worker]放入任务[8]成功

11:03:46.965 [Test worker] INFO c.e.d.DemoApplicationTests9 - 线程池已到达最大限度,任务[9]尝试进入队列

11:03:46.965 [Test worker] INFO c.e.d.DemoApplicationTests9 - [Test worker]开始尝试放入任务[9]

11:03:46.965 [Test worker] INFO c.e.d.DemoApplicationTests9 - 任务[9]成功进入队列,[Test worker]

11:03:46.966 [Test worker] INFO c.e.d.DemoApplicationTests9 - 尝试打断[t0]

java.lang.InterruptedException: sleep interrupted