线程池简单源码思路手撕实现和关于参数设置

线程池简单源码思路手撕实现

注意这里的代码只是线程池的简单逻辑实现,没考虑多线程加锁什么的

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class myThreadPool {
    public BlockingQueue<Runnable> blockingQueue;
    private int corePoolSize;
    private int maxPoolSize;
    private int timeout;
    private TimeUnit timeUnit;
    private RejectHandle rejectHandle;
    private List<Thread> coreList = new ArrayList<>();
    private List<Thread> supportList = new ArrayList<>();

    public myThreadPool(int corePoolSize, int maxPoolSize, int timeout, TimeUnit timeUnit, BlockingQueue<Runnable> blockingQueue, RejectHandle rejectHandle) {
        this.corePoolSize = corePoolSize;
        this.maxPoolSize = maxPoolSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.blockingQueue = blockingQueue;
        this.rejectHandle = rejectHandle;
    }

    void execute(Runnable command) {
        if (coreList.size() < corePoolSize) {
            CoreThread thread = new CoreThread(command);
            coreList.add(thread);
            thread.start();
            return;
        }
        if (blockingQueue.offer(command)) {
            return;
        }
        if (coreList.size() + supportList.size() < maxPoolSize) {
            SupportThread thread = new SupportThread(command);
            supportList.add(thread);
            thread.start();
            return;
        }

        if (!blockingQueue.offer(command)) {
            rejectHandle.reject(command, this);
        }
    }

    class CoreThread extends Thread {
        private final Runnable firstTask;

        CoreThread(Runnable firstTask) {
            this.firstTask = firstTask;
        }

        @Override
        public void run() {
            firstTask.run();
            while (true) {
                try {
                    Runnable command = blockingQueue.take();
                    command.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    class SupportThread extends Thread {
        private final Runnable firstTask;

        SupportThread(Runnable firstTask) {
            this.firstTask = firstTask;
        }

        @Override
        public void run() {
            firstTask.run();
            while (true) {

                try {
                    Runnable command = blockingQueue.poll(timeout, timeUnit);
                    if (command == null) {
                        break;
                    }
                    command.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName()+"线程死掉了");
            supportList.remove(Thread.currentThread());

        }
    }
}
首先是重要的线程池参数:

核心线程数:定义不会被回收的核心线程数量

最大线程数:定义除了核心线程以外的临时线程,临时线程超过时间还没任务就会被销毁

阻塞队列:选择用什么阻塞队列,使用阻塞队列是防止在核心/辅助线程再去拿任务的时候一直while对cpu空转的损害

拒绝策略:在超过最大线程就会采取对应的拒绝策略-比如报错舍弃/静默丢弃/舍弃旧任务(或者也可以考虑持久化到mysql保存起来)

时间:这个是辅助线程没任务的存活时间。

时间单位:这个是辅助线程没任务的存活时间的单位。

线程工厂:用来决定线程的命名,创建线程的方式,管理线程优先级之类的。这里只是简单demo所以没有实现。

java 复制代码
  void execute(Runnable command) {
        if (coreList.size() < corePoolSize) {
            CoreThread thread = new CoreThread(command);
            coreList.add(thread);
            thread.start();
            return;
        }
        if (blockingQueue.offer(command)) {
            return;
        }
        if (coreList.size() + supportList.size() < maxPoolSize) {
            SupportThread thread = new SupportThread(command);
            supportList.add(thread);
            thread.start();
            return;
        }

        if (!blockingQueue.offer(command)) {
            rejectHandle.reject(command, this);
        }
    }
这个是重要的线程池接受任务的过程:

核心线程没满就用核心线程,满了就先放到阻塞队列,阻塞队列满了就创建临时辅助线程,辅助线程也满了就考虑拒绝策略。

java 复制代码
 class CoreThread extends Thread {
        private final Runnable firstTask;

        CoreThread(Runnable firstTask) {
            this.firstTask = firstTask;
        }

        @Override
        public void run() {
            firstTask.run();
            while (true) {

                try {
                    Runnable command = blockingQueue.take();
                    command.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }

    class SupportThread extends Thread {
        private final Runnable firstTask;

        SupportThread(Runnable firstTask) {
            this.firstTask = firstTask;
        }

        @Override
        public void run() {
            firstTask.run();
            while (true) {

                try {
                    Runnable command = blockingQueue.poll(timeout, timeUnit);
                    if (command == null) {
                        break;
                    }
                    command.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(Thread.currentThread().getName()+"线程死掉了");
            supportList.remove(Thread.currentThread());

        }
    }
}

这里有firsttask的任务主要是在线程刚创建就来执行,减少再去阻塞队列拿的麻烦,核心线程和辅助线程从阻塞队列拿任务的方法分别是take()和poll()主要是后者限制时间,辅助线程执行完就会被销毁这里是发生在run()执行完。而take()永久等待出不来就不会被销毁。

然后看一下拒绝策略
java 复制代码
package tech.insight;

/**
 * @author gongxuanzhangmelt@gmail.com
 **/
public interface RejectHandle {

    void reject(Runnable rejectCommand, MyThreadPool threadPool);
}
java 复制代码
package tech.insight;

/**
 * @author gongxuanzhangmelt@gmail.com
 **/
public class ThrowRejectHandle implements RejectHandle {
    @Override
    public void reject(Runnable rejectCommand, MyThreadPool threadPool) {
        throw new RuntimeException("阻塞队列满了!");
    }
}
java 复制代码
package tech.insight;

/**
 * @author gongxuanzhangmelt@gmail.com
 **/
public class DiscardRejectHandle implements RejectHandle {
    @Override
    public void reject(Runnable rejectCommand, MyThreadPool threadPool) {
        threadPool.blockingQueue.poll();
        threadPool.execute(rejectCommand);
    }
}

只实现了报错丢弃和丢弃旧任务加入新任务。

java 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {

        myThreadPool myThreadPool = new myThreadPool(2, 10, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new ThrowRejectHandle());
        for (int i = 0; i < 10; i++) {
            final  int fi=i;
            myThreadPool.execute(()->{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName()+" "+fi);
            });
        }
    }
}

main里初始化线程池,以及添加10个任务,以上就是线程池的简单实现。

接下来我记录一些自己看的扩展

关于参数该怎么设置?比如核心线程数和最大线程数和其他的?

线程数

首先看八股怎么说:

cpu密集型:核心线程数=cpu核心数加减1,最大线程数=cpu核心数加减1

io密集型:核心线程数=cpu核心数*2,最大线程数=CPU核心数 × 4 或更高

然后还有具体的场景:

比如在电商场景:流量波动极大。平时可能没什么人,一旦活动开启,流量瞬间暴涨。可以极端的把核心线程数设置为0(还是留一点但是占比小),全是临时线程没有任务就回收。

还有记录日志场景:这种一般没什么变化,就全部设置为核心线程数就好,最大线程数等于核心线程数。

阻塞队列

阻塞队列选有界的如 ArrayBlockingQueueLinkedBlockingQueue(n),因为无界队列存在内存溢出风险,还有**SynchronousQueue:** 之前提到的电商核心线程为 0 时常用。它不存任务,直接"手递手"交给线程。

关于存活时间

电商/高并发场景: 建议设置得稍长一些(如 60s)。因为流量往往是波动性的,频繁地销毁和重新创建线程是非常消耗 CPU 的。

后台低频任务: 建议设置得短一些(如 1s 或 10s)。任务处理完赶紧释放资源。

默认参考: Java 自带的 CachedThreadPool 默认是 60s,这是一个非常合理的平衡点点。

可以理解为核心线程少就长一点,多就短一点。

拒绝策略

当线程池满了(最大线程已开,队列也塞满了),新来的任务怎么办?Java 提供了四种标准策略:

  1. AbortPolicy(默认): 直接抛出 RejectedExecutionException
    • 适用: 关键业务,必须让调用方知道任务失败了。
  2. CallerRunsPolicy(调用者运行): 谁提交的任务,谁自己去执行。
    • 适用: 最重要的兜底方案。它会减慢生产者的速度(因为生产者去干活了,没空发任务了),起到一种天然的"降级"和"限流"作用。
  3. DiscardPolicy: 直接丢掉任务,不报任何错。
    • 适用: 比如打印无关紧要的日志、监控埋点,丢了就丢了。
  4. DiscardOldestPolicy: 丢弃队列里排队最久(最老)的任务,把当前任务塞进去。
    • 适用: 具有"时效性"的任务,比如实时行情数据,旧的数据没意义了。

根绝业务性质来选择拒绝策略。

还有美团的动态线程池是怎么搞的?

先讲讲为什么有这个需求?

所谓的"美团动态线程池",核心本质就是:配置中心(如 Nacos/Apollo) + JDK 线程池原生 API + 监控告警

它不是创造了一种新的线程池技术,而是一套管理和运维线程池的架构方案。这个概念最早源于美团技术团队发表的一篇经典文章《Java线程池实现原理及其在美团业务中的实践》,后来成为了业界标准。

传统痛点: 线程池参数(核心数、最大数、队列长度)通常写死在代码或配置文件里。一旦上线,发现流量太大导致队列积压,或者参数设置不合理导致 CPU 飙升,必须改代码 -> 重新打包 -> 重启服务。这在"双11"这种分秒必争的时刻是致命的。

动态线程池能做到什么:

热更新: 像开关灯一样,运营/开发在后台修改参数,服务端的线程池瞬间生效,无需重启

全景监控: 实时看到线程池在干嘛(队列堆了多少、当前活跃线程多少)。

具体是怎么实现的?

它的实现并不复杂,主要分为三步:管理、监听、修改

第一步:管理(注册中心)

也就是你要把系统里所有的线程池都管理起来。通常会做一个 ThreadPoolManager,用一个 Map 把所有线程池存起来,每个线程池有个名字(比如 order-pool, log-pool)。

第二步:监听(配置中心)

这是关键。利用 Nacos、Apollo、Etcd 等配置中心的能力。

  • 你在 Nacos 上修改配置:order-pool.coreSize = 20
  • 应用程序里的监听器(Listener)捕捉到配置变化。(只要改了主动推送)

第三步:修改(JDK API - 很多人不知道的秘密)

这是最核心的。很多人以为线程池创建了就不能改,其实 JDK 的 ThreadPoolExecutor 原生就提供了 public 的 set 方法

这里还有一个点,默认的阻塞队列是final的想要修改长度只能自己实现阻塞队列并去掉final。

还要配合定时任务去监控读取运行时指标保证安全,也要监控队列使用率比如超过80%报警。

省流:

"其实参数设置没有绝对的标准,核心准则是:即不要让 CPU 闲置(吞吐量太低),也不要让队列积压导致 OOM(内存溢出)。

所以,动态线程池 才是最终的解决方案。因为线上流量是未知的,我们无法在写代码时就预测出最完美的参数,只有具备了动态调整可视化监控的能力,才能让系统在面对突发流量时立于不败之地。"

相关推荐
CRMEB8 小时前
高品质开源电商系统的技术内核:架构设计与技术优势
ai·开源·php·免费源码·源代码管理·商城源码
码界奇点1 天前
基于SpringBoot与Shiro的细粒度动态权限管理系统设计与实现
java·spring boot·后端·spring·毕业设计·源代码管理
码界奇点1 天前
基于Spring Boot与MyBatis-Plus的后台管理系统设计与实现
spring boot·后端·车载系统·毕业设计·mybatis·源代码管理
码界奇点1 天前
基于Spring+SpringMVC+MyBatis+easyUI的后台管理系统设计与实现
java·spring·毕业设计·mybatis·easyui·源代码管理
zgl_200537792 天前
ZGLanguage 解析SQL数据血缘 之 Python + Echarts 显示SQL结构图
大数据·数据库·数据仓库·hadoop·sql·代码规范·源代码管理
码界奇点3 天前
基于Spring Cloud微服务架构的电商系统设计与实现
spring cloud·微服务·架构·毕业设计·鸿蒙系统·源代码管理
码界奇点3 天前
基于Spring与Netty的分布式配置管理系统设计与实现
java·分布式·spring·毕业设计·源代码管理
码界奇点4 天前
基于Vue3与TypeScript的后台管理系统设计与实现
前端·javascript·typescript·vue·毕业设计·源代码管理
期货资管源码7 天前
外盘期货资管分仓软件源码搭建教程
大数据·源代码管理