Tomcat线程池原理(下篇:工作原理)

本文详细分析了Tomcat线程池如何基于JUC的ThreadPoolExecutor实现,特别关注了其对阻塞队列TaskQueue的改造,讨论了线程执行流程、offer和force方法的行为,以及Tomcat如何优化适应IO密集型任务。
摘要由CSDN通过智能技术生成

前言

Tomcat 线程池,是依据 JUC 中的线程池 ThreadPoolExecutor 重新自定义实现的。
其执行线程的代码逻辑,和JUC 中是相同的。主要区别在于,Tomcat中对 阻塞队列进行了改造。

本文主要研究 Tomcat 的线程池是如何执行线程的,即线程池的工作原理。

同系列文章:Tomcat线程池原理(上篇:初始化原理)

正文

一、执行线程的基本流程

Tomcat 中执行线程的基本流程,和JUC中是一致的。
以下贴出两种执行方法。

1.1 JUC中的线程池执行线程

ThreadPoolExecutor中,执行线程的方法定义如下:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

1.2 Tomcat 中线程池执行线程

@Override
public void execute(Runnable command) {
    execute(command,0,TimeUnit.MILLISECONDS);
}

重载方法定义如下:
这个方法被Deprecated标注,源码中注释中描述说是,Tomcat10中会被删除,估计是一种优化。本文不做研究。

@Deprecated
public void execute(Runnable command, long timeout, TimeUnit unit) {
	// 提交的任务数量+1
    submittedCount.incrementAndGet();
    try {
    	// 执行线程任务(这个方法中的代码和JUC中执行线程的代码一致,差别在于,阻塞队列使用了Tomcat自定义的队列)
        executeInternal(command);
    } catch (RejectedExecutionException rx) {
    	// 被拒绝后,尝试将线程放入队列
    	// 如果当前队列是Tomcat自定义的队列TaskQueue,尝试将任务放入队列
        if (getQueue() instanceof TaskQueue) {
            final TaskQueue queue = (TaskQueue) getQueue();
            try {
            	// 强制将线程任务放入阻塞队列
                if (!queue.force(command, timeout, unit)) {
                	// 放入队列失败,提交的任务数-1
                    submittedCount.decrementAndGet();
                    // 抛出异常,线程池队列已满
                    throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
                }
            } catch (InterruptedException x) {
            	// 中断时,提交的任务数-1
                submittedCount.decrementAndGet();
                // 抛出异常
                throw new RejectedExecutionException(x);
            }
        } else {
        	// 当前指定的队列不是TaskQueue,提交的任务数-1,并抛出异常
            submittedCount.decrementAndGet();
            throw rx;
        }
    }
}

另外,executeInternal 的内容如下(和JUC中基本一致):区别在于阻塞队列换成了TaskQueue

private void executeInternal(Runnable command) {
    if (command == null) {
        throw new NullPointerException();
    }
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) {
            return;
        }
        c = ctl.get();
    }
    // 这里的workQueue,实际已经换成了TaskQueue
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command)) {
            reject(command);
        } else if (workerCountOf(recheck) == 0) {
            addWorker(null, false);
        }
    }
    else if (!addWorker(command, false)) {
        reject(command);
    }
}

二、被改造的阻塞队列

从第一小节中,看到的东西并不多。因为,Tomcat线程池的改造重心,在于阻塞队列,也就是 TaskQueue

2.1 TaskQueue的 offer(…)

@Override
public boolean offer(Runnable o) {
  //we can't do any checks
    if (parent==null) {
        return super.offer(o);
    }
    // 若是达到最大线程数,直接进队列
    if (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) {
        return super.offer(o);
    }
    // 已提交任务数小于等于当前线程数
    // 对应的场景是:当前活跃线程为10个,但是只有8个任务在执行,于是,直接进队列
    if (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) {
        return super.offer(o);
    }
    // 当前线程数小于最大线程数,此次拒绝进入队列
    if (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) {
        return false;
    }
    //if we reached here, we need to add it to the queue
    return super.offer(o);
}

2.2 TaskQueue的 force(…)

其本质是,直接入队列。

public boolean force(Runnable o) {
    if (parent == null || parent.isShutdown()) {
        throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
    }
    return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
}

三、总结

根据源码中对于TaskQueue的改造,可以观察出,Tomcat线程池的主要特点如下:

  • 当前线程数小于corePoolSize,则去创建工作线程;
  • 当前线程数大于corePoolSize,但小于maximumPoolSize,则去创建工作线程;
  • 当前线程数大于maximumPoolSize,则将任务放入到阻塞队列中,当阻塞队列满了之后,则调用拒绝策略丢弃任务;
  • 任务执行失败时不会直接抛出错误,而是装回队列里再次尝试执行;
  • 当线程池没有达到最大执行线程的时候,会优先开线程再使用任务队列;

总结之后就是,Tomcat 为了更适配 IO 密集型任务,改造了阻塞队列。与JUC相比,会先去创建线程执行任务,创建的线程数达到最大线程数时,再放入队列等待空闲线程的出现。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你家宝宝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值