参考Dubbo实现基于JDK线程池的快速消费线程池

本文基于 “如何解决 JDK 线程池中不超过最大线程数下即时快速消费任务,而不是在队列中堆积”这个问题,在JDK线程池的基础上做出一些调整,实现任务快速消费。

JDK线程池

工作流程

JDK线程池的执行流程如下:

①当前工作线程数 < 核心线程数corePoolSize:创建核心线程执行任务

  • 核心线程长久存在,即使空闲也不会销毁,除非配置 allowCoreThreadTimeOut = true,那么核心线程就会在空闲keepAliveTime后被销毁 

②当前工作线程数 >= 核心线程数:将任务加入阻塞队列

③阻塞队列已满:创建非核心线程执行任务

  • 非核心线程数 + 核心线程数corePoolSize = 最大线程数maximumPoolSize

④工作线程 >= 最大线程数:执行拒绝策略,默认是抛出异常

贴源码

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        //获取线程池信息
        int c = ctl.get();

        //workerCountOf(c):得到当前工作线程数
        if (workerCountOf(c) < corePoolSize) {    //当前工作线程数 < 核心线程数
            //addWorker(command, true):尝试创建核心线程
            //true为创建核心线程,false为创建非核心线程
            if (addWorker(command, true))
                //创建成功则结束
                return;
            //创建失败,再次获取线程池信息
            c = ctl.get();
        }
        
        //isRunning(c):线程池是RUNNING状态,即线程池还在运行
        //workQueue.offer(command):将任务加入阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            //再次获取线程池状态
            int recheck = ctl.get();
            //检查,如果线程池终止运行了,移除任务
            if (! isRunning(recheck) && remove(command))
                //执行拒绝策略
                reject(command);
            //线程池仍然在运行,但工作线程数为0,创建非核心线程处理队列中滞留任务
            else if (workerCountOf(recheck) == 0)
                //null表示处理阻塞队列中的任务,false表示创建非核心线程
                addWorker(null, false);
        }
        //线程池终止运行,创建非核心线程处理队列中滞留任务
        //或者 线程在运行,但添加任务到阻塞队列失败,即阻塞队列已满
        else if (!addWorker(command, false))
            //如果创建非核心线程处理任务失败,执行拒绝策略
            reject(command);
    }

Q:为什么将任务加入阻塞队列后,要再次获取线程池状态,进行检查?

A:因为可能任务进入阻塞队列后,线程池停止运行了,那么这个任务就会滞留在队列中,永远无法被执行,成为“僵尸任务”。

因此JDK做了双重检查,在任务成功入队后,再次检查线程池状态,如果线程池停止运行了,会将任务从队列中移除,如果移除成功,会执行拒绝策略,告知调用者,这任务我们做不了了。

但也可能存在移除任务失败的情况,如果任务已被其它线程消费,那么remove()失败,不会执行拒绝策略。

Q:线程池仍然在运行,工作线程数为0如何理解?

A:这个场景是线程池仍然是RUNNING状态,但所有线程异常终止,比如核心线程因为超时销毁(开启了allowCoreThreadTimeOut =true的配置),这时就需要创建一个非核心线程来执行掉这个任务。

再Q:为什么是创建非核心线程处理,而不是创建核心线程处理?

再A:可能是由于两者管理原则的区别,非核心线程作为弹性资源,只是短暂救急。还有一个说法是避免核心线程膨胀:如果每次无线程存活都创建核心线程,可能导致 corePoolSize 被隐性突破,违背配置约束。

到这里,应该就比较清楚JDK线程池的工作流程了。

快速消费线程池

工作流程

本文要实现的快速消费线程池,主要就是对上述流程做一个顺序调整:

①当前工作线程数 < 核心线程数:创建核心线程执行任务

②否则,创建非核心线程执行任务

③已达最大线程数,将任务加入阻塞队列

④阻塞队列满,执行拒绝策略

可以看到,就是在达到核心线程数后,接着创建非核心线程执行任务而不是把任务丢入阻塞队列,从而达到一个”快速执行任务“的效果。

代码实现

逻辑:快速消费线程池仍然是继承自JDK线程池实现ThreadPoolExecutor,新增AtomicInteger类属性 submittedTaskCount 记录当前线程池正在处理的任务数量(正在执行的任务数+队列中的任务数),我们就可以通过submittedTaskCount对比任务数和线程数,从而选择将任务放入队列给已有线程处理,还是新建线程去处理。

提交任务后,线程池执行execute()方法,核心线程数满后会执行阻塞队列的offer()方法,我们通过自定义阻塞队列,重写其中的offer()方法,让工作线程数达到核心线程数后,返回false,那么在JDK源码中,它就会去创建非核心线程执行任务。

只有工作线程数达到最大线程数时,才会将任务加入阻塞队列。

这样,就达到了我们调换顺序,提升消费任务速度的目的。

自定义任务队列

import lombok.Setter;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 快速消费任务队列
 */
public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
    @Setter
    private EagerThreadPoolExecutor executor;

    public TaskQueue(int capacity) {
        super(capacity);
    }

    @Override
    public boolean offer(Runnable runnable) {
        int currentPoolThreadSize = executor.getPoolSize();
        // 如果线程池中有线程正在空闲,将任务加入阻塞队列,由空闲线程去处理任务
        if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
            return super.offer(runnable);
        }
        //【重点】
        // 当前线程池线程数量小于最大线程数,返回 False,根据线程池源码,会创建非核心线程
        if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
            return false;
        }
        // 如果当前线程池数量大于最大线程数,任务加入阻塞队列
        return super.offer(runnable);
    }

    //重试将任务加入队列
    public boolean retryOffer(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
        //如果线程池被关闭,直接抛出拒绝策略
        if (executor.isShutdown()) {
            throw new RejectedExecutionException("Executor is shutdown!");
        }
        return super.offer(o, timeout, unit);
    }
}

快速消费线程池

import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 快速消费线程池
 */
public class EagerThreadPoolExecutor extends ThreadPoolExecutor {
    
    public EagerThreadPoolExecutor(int corePoolSize,
                                   int maximumPoolSize,
                                   long keepAliveTime,
                                   TimeUnit unit,
                                   TaskQueue<Runnable> workQueue,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }
    
    //已提交任务数,即线程池中正在处理的任务数
    private final AtomicInteger submittedTaskCount = new AtomicInteger(0);

    public int getSubmittedTaskCount() {
        return submittedTaskCount.get();
    }

    /**
     * 执行完任务后,submittedTaskCount-1
     */
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        submittedTaskCount.decrementAndGet();
    }

    @Override
    public void execute(Runnable command) {
        //有任务来执行,submittedTaskCount+1
        submittedTaskCount.incrementAndGet();
        try {
            super.execute(command);
        } catch (RejectedExecutionException ex) {
            //按当前流程,阻塞队列满后,任务加入会被拒绝
            TaskQueue taskQueue = (TaskQueue) super.getQueue();
            try {
                //这时再次尝试加入队列,重试机制,如果加入失败,立即拒绝
                if (!taskQueue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {
                    submittedTaskCount.decrementAndGet();
                    throw new RejectedExecutionException("Queue capacity is full.", ex);
                }
            } catch (InterruptedException iex) {
                submittedTaskCount.decrementAndGet();
                throw new RejectedExecutionException(iex);
            }
        } catch (Exception ex) {
            submittedTaskCount.decrementAndGet();
            throw ex;
        }
    }
}

此外,在队列已满,任务加入队列失败,抛出异常时,我们对其进行捕获,使用重试机制,再次尝试将任务放入队列,因为队列中的任务可能很快被消费,此时重试入队可以避免不必要的拒绝

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值