从构建有界延迟队列线程池看线程池运转机制

1,343 阅读3分钟

How time flies! 好久没更了,估计有些小伙伴都忘了曾经关注过这个账号,突然看到陌生人,估计就把我删了^^,没关系,感谢你曾经关注过我这个默默无闻的做点小技术的coder。写东西确实挺耗时间的,反思之前的文章,体系和条理即突出重点做的稍差一点,未来会争取多读多写,还是一样,只写原创,内容不一定多有深度,甚至可能会有错,但是如果觉得对小伙伴有帮助,就会整理出来,不论短长。

闲言少叙,书接N个上回。

线程池是平时工作和面试的重点,最近要做些历史数据的割接:历史数据部署在本地系统中,要上传到云端Saas中去(中间有数据模型转换),数据割接完成后,会有关键指标一致性校验来兜底数据上传的完整性和准确性,但是整个过程需要监控,以便能更好的定位和发现数据上传的问题以及掌握数据上传的进度。

设计如下:在本地单个或整体上传任务完成后,都会向云端发起任务完成的通知,云端根据设置时间延迟查库和上传通知做比对,进行告警或结束提示,整个过程有统一trace的日志跟踪,以供数据不平时的分析,上传链路经过隔离的消息队列,增加限流(限流也是工作和面试的重点,可搜索令牌桶和漏桶等算法了解其区别)减少对正常业务的干扰。

由于数据传递有延迟,于是云端需要延迟根据上传通知做订单量匹配校验,另外上传细粒度任务可能非常多,为了减少对系统内存的压力,希望这个队列是有界的,搜了下concurrent包,没找到合适的轮子。

开始造轮子。

  • 造个有界延迟队列 DelayQueue是无界延迟队列,借用其延迟功能,增加有界处理,当size达到capacity时,返回false。同步处理需要持有父类DelayQueue同一把lock,因此通过反射得到,详见构造函数。
public class BoundedDelayQueue<T extends DelayTask> extends DelayQueue<T> {
    private final transient ReentrantLock lock;

    private final int capacity;

    BoundedDelayQueue(int capacity) {
        try {
            Field lockField = DelayQueue.class.getDeclaredField("lock");
            lockField.setAccessible(true);
            this.lock = (ReentrantLock)lockField.get(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error("Could not access lock field", e);
        }
        this.capacity = capacity;
    }

    @Override
    public boolean offer(final T t) {
        lock.lock();
        try {
            if (size() == capacity) {
                return false;
            }
            return super.offer(t);
        } finally {
            lock.unlock();
        }
    }
}
  • 以有界延迟队列手撸线程池 线程池继承Spring提供的ThreadPoolTaskExecutor,重写setMaxPoolSize、initialize、createQueue方法,为什么要重写这几个方法: 1、createQueue:根据传入的capacity,决定构建有界延迟队列还是无界; 2、setMaxPoolSize:如不重写,会导致部分任务无法得到延迟处理,稍后解读; 3、initialize:如不重写,会导致coreSize的任务无法得到延迟处理,稍后解读。 代码如下:
public class ThreadPoolDelayTaskExecutor extends ThreadPoolTaskExecutor {
    @Override
    public void setMaxPoolSize(int maxPoolSize) {
        // 最大线程数和核心线程数保持一致
        super.setMaxPoolSize(super.getCorePoolSize());
    }

    @Override
    public void initialize() {
        super.initialize();
        // 预启动全部核心线程,以发挥延迟队列的作用
        super.getThreadPoolExecutor().prestartAllCoreThreads();
    }

    @Override
    public BlockingQueue<Runnable> createQueue(int queueCapacity) {
        if (queueCapacity > 0) {
            return new BoundedDelayQueue(queueCapacity);
        } else {
            // 无界延迟队列
            return new DelayQueue();
        }
    }
}

线程池是如何运行的

见下面简陋的小鸡吃米图,上图为提交任务到线程池的完整执行流程,下图为线程池内线程的状态流程图。

线程池运行机制.png
可以看到需要确保核心线程已经预启动且最大线程数与核心线程数相同,才能确保线程执行任务都是通过延迟队列获得,即任务得到延迟处理。

演讲要像女孩子的裙子一样越短越好,码字亦如是。

68号小喇叭