线程池深度解析:核心参数 + 拒绝策略 + 动态调整实战

线程池深度解析:核心参数 + 拒绝策略 + 动态调整实战

作为一名拥有八年经验的 Java 后端高级开发,我见过太多因线程池使用不当导致的线上问题:高峰期任务堆积 OOM、线程数过多导致 CPU 上下文切换飙升、拒绝策略配置不合理丢失核心业务请求...... 线程池看似简单,实则是高并发场景下的核心利器,也是面试中的必考点。

本文将从核心参数原理拒绝策略选型动态调整实战 三个维度,结合生产环境经验,带你彻底吃透线程池,文末还会附上我在项目中封装的通用线程池工具类,可直接 CV 使用!

一、线程池核心原理:ThreadPoolExecutor 核心参数深度解析

Java 中的线程池核心实现是 ThreadPoolExecutor,其构造方法如下:

arduino 复制代码
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    // 源码实现
}

这 7 个核心参数决定了线程池的行为,下面逐一拆解,结合实际场景讲解每个参数的作用和选型技巧。

1. 核心线程数(corePoolSize):线程池的常驻线程数

  • 定义 :线程池维护的最小线程数,即使线程处于空闲状态,也不会被销毁(除非设置了 allowCoreThreadTimeOut)。

  • 实战选型

    • CPU 密集型任务(如计算、排序):corePoolSize = CPU核心数 + 1,减少上下文切换。
    • IO 密集型任务(如数据库操作、网络请求):corePoolSize = CPU核心数 * 2CPU核心数 / (1 - 阻塞系数),充分利用 CPU 资源。
    • 经验值:对于大多数后端服务,corePoolSize 通常设置为 8~32,具体需结合压测结果调整。

2. 最大线程数(maximumPoolSize):线程池的扩容上限

  • 定义:线程池允许创建的最大线程数,当核心线程数已满且任务队列已满时,线程池会创建新线程,直到达到该上限。
  • 核心注意点maximumPoolSize 只有在任务队列满了 之后才会生效!如果使用无界队列(如 LinkedBlockingQueue),该参数将永远不会被触发。
  • 实战选型maximumPoolSize 应大于 corePoolSize,通常设置为 corePoolSize * 2 或根据业务峰值流量调整,避免过度扩容导致系统资源耗尽。

3. 空闲线程存活时间(keepAliveTime + unit):线程池的收缩机制

  • 定义 :当线程池中的线程数超过 corePoolSize 时,空闲线程的存活时间,超过该时间后,空闲线程会被销毁,直到线程数等于 corePoolSize
  • 实战选型:IO 密集型任务可设置较长的存活时间(如 30 秒),CPU 密集型任务可设置较短的存活时间(如 10 秒),避免空闲线程占用资源。

4. 任务队列(workQueue):核心线程池的 "缓冲池"

  • 定义:用于存储等待执行的任务的阻塞队列,当核心线程数已满时,新任务会被加入队列。

  • 常用队列类型及选型

    队列类型 特点 适用场景
    ArrayBlockingQueue 有界队列,初始化时指定容量 生产环境首选,可避免任务无限堆积导致 OOM
    LinkedBlockingQueue 无界队列(默认容量为 Integer.MAX_VALUE 不推荐在高并发场景使用,容易导致 OOM
    SynchronousQueue 同步队列,不存储任务,直接传递给线程 适合任务执行时间极短的场景,需配合 maximumPoolSize = Integer.MAX_VALUE 使用
    PriorityBlockingQueue 优先级队列,按任务优先级执行 适合需要优先执行核心任务的场景
  • 高级开发经验生产环境必须使用有界队列!并合理设置队列容量,结合拒绝策略一起使用,避免任务堆积导致系统崩溃。

5. 线程工厂(threadFactory):线程的 "创建器"

  • 定义:用于创建线程的工厂,可自定义线程名称、优先级、是否为守护线程等。
  • 核心作用 :自定义线程名称,方便在日志和监控中排查问题。例如,将线程名称设置为 pool-name-thread-1,可以快速定位到某个线程池的线程。
  • 实战示例 :使用 guavaThreadFactoryBuilder 或自定义 ThreadFactory
scss 复制代码
ThreadFactory threadFactory = new ThreadFactoryBuilder()
    .setNameFormat("order-pool-%d")
    .setDaemon(false)
    .build();

6. 拒绝策略(handler):任务队列满了之后的 "兜底方案"

  • 定义 :当线程池达到 maximumPoolSize 且任务队列已满时,新任务的处理策略。
  • 核心重要性:拒绝策略的选择直接影响业务的可用性,不合理的拒绝策略会导致核心任务丢失。
  • 这部分内容非常重要,我们单独开一个章节深度解析。

二、拒绝策略深度解析:4 种默认策略 + 自定义策略实战

线程池的拒绝策略是 RejectedExecutionHandler 接口的实现,JDK 提供了 4 种默认策略,同时我们也可以自定义拒绝策略。

1. JDK 4 种默认拒绝策略

策略名称 特点 适用场景
AbortPolicy(默认) 直接抛出 RejectedExecutionException 异常 适合核心业务,需要快速感知任务提交失败的场景
CallerRunsPolicy 由提交任务的线程执行该任务 适合非核心业务,允许任务在调用线程中执行的场景,可避免任务丢失
DiscardPolicy 直接丢弃新任务,不抛出异常 适合非核心业务,允许任务丢失的场景,如日志收集、数据统计
DiscardOldestPolicy 丢弃队列中最旧的任务,然后尝试提交新任务 适合任务执行时间较短,旧任务的优先级低于新任务的场景

2. 自定义拒绝策略:生产环境必备

在实际项目中,默认的拒绝策略往往无法满足需求,例如,我们需要在任务被拒绝时,记录日志、发送告警、持久化任务到数据库等。此时,我们可以自定义拒绝策略。

实战需求:当任务被拒绝时,记录任务详情到日志,并发送告警邮件,同时尝试将任务持久化到 Redis,待系统恢复后重试。

自定义拒绝策略实现

typescript 复制代码
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {

    private static final Logger logger = LoggerFactory.getLogger(CustomRejectedExecutionHandler.class);

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 1. 记录任务详情
        String taskInfo = r.toString();
        logger.error("线程池任务被拒绝,任务信息:{},线程池状态:{}", taskInfo, getThreadPoolStatus(executor));

        // 2. 发送告警邮件(此处省略邮件发送逻辑)
        // AlertUtil.sendAlert("线程池任务被拒绝", "任务信息:" + taskInfo);

        // 3. 持久化任务到Redis(此处省略Redis持久化逻辑)
        try {
            RedisUtil.lPush("thread_pool_rejected_tasks", taskInfo);
        } catch (Exception e) {
            logger.error("持久化被拒绝任务到Redis失败", e);
        }

        // 4. 可选:抛出异常,根据业务需求决定
        throw new RejectedExecutionException("Task " + r + " rejected from " + executor);
    }

    /**
     * 获取线程池状态
     */
    private String getThreadPoolStatus(ThreadPoolExecutor executor) {
        return String.format("核心线程数:%d,最大线程数:%d,当前线程数:%d,活跃线程数:%d,任务队列大小:%d,已完成任务数:%d",
                executor.getCorePoolSize(),
                executor.getMaximumPoolSize(),
                executor.getPoolSize(),
                executor.getActiveCount(),
                executor.getQueue().size(),
                executor.getCompletedTaskCount());
    }
}

3. 拒绝策略选型最佳实践

  • 核心业务 :使用 AbortPolicy + 自定义异常处理,快速感知任务提交失败,同时记录日志和发送告警。
  • 非核心业务 :使用 CallerRunsPolicy 或自定义拒绝策略,避免任务丢失。
  • 日志、统计等低优先级业务 :使用 DiscardPolicyDiscardOldestPolicy
  • 高级经验 :拒绝策略必须和有界队列配合使用,否则拒绝策略永远不会生效!

三、动态调整线程池参数:从理论到实战,应对流量波动

在实际项目中,业务流量往往是动态变化的,例如,电商平台的秒杀活动、双十一的流量峰值,以及日常的低流量时段。如果线程池参数固定不变,可能会导致在峰值时系统资源不足,在低峰时资源浪费。

因此,动态调整线程池参数 是高级 Java 开发必须掌握的技能。ThreadPoolExecutor 提供了一系列 set 方法,允许我们在运行时调整核心参数:

  • setCorePoolSize(int corePoolSize):调整核心线程数
  • setMaximumPoolSize(int maximumPoolSize):调整最大线程数
  • setKeepAliveTime(long time, TimeUnit unit):调整空闲线程存活时间
  • setRejectedExecutionHandler(RejectedExecutionHandler handler):调整拒绝策略

1. 动态调整的核心思路

  1. 监控线程池状态 :通过线程池的 get 方法获取当前状态,如活跃线程数、队列大小、已完成任务数等。
  2. 根据业务指标调整参数:根据 QPS、响应时间、CPU 使用率等业务指标,动态调整核心线程数和最大线程数。
  3. 结合配置中心:使用 Nacos、Apollo 等配置中心,实现线程池参数的动态配置,无需重启应用。

2. 实战:结合 Nacos 实现线程池参数动态调整

下面我们以 Nacos 为例,实现线程池参数的动态调整。核心步骤是:监听 Nacos 配置变化,当配置发生变化时,调用线程池的 set 方法调整参数。

步骤 1:添加 Nacos 依赖
xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
步骤 2:配置 Nacos 监听
java 复制代码
@Configuration
@RefreshScope
public class ThreadPoolConfig {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPoolConfig.class);

    @Value("${thread.pool.corePoolSize:8}")
    private int corePoolSize;

    @Value("${thread.pool.maximumPoolSize:16}")
    private int maximumPoolSize;

    @Value("${thread.pool.keepAliveTime:30}")
    private long keepAliveTime;

    @Value("${thread.pool.queueCapacity:1000}")
    private int queueCapacity;

    /**
     * 创建线程池
     */
    @Bean(name = "orderThreadPool")
    public ThreadPoolExecutor orderThreadPool() {
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat("order-pool-%d")
                .build();

        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(queueCapacity);

        return new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.SECONDS,
                workQueue,
                threadFactory,
                new CustomRejectedExecutionHandler()
        );
    }

    /**
     * 监听Nacos配置变化,动态调整线程池参数
     */
    @EventListener(RefreshEvent.class)
    public void refreshThreadPool(RefreshEvent event) {
        ThreadPoolExecutor executor = SpringContextUtil.getBean("orderThreadPool", ThreadPoolExecutor.class);
        if (executor == null) {
            logger.error("获取线程池失败");
            return;
        }

        // 调整核心线程数
        if (corePoolSize != executor.getCorePoolSize()) {
            executor.setCorePoolSize(corePoolSize);
            logger.info("动态调整核心线程数:{} -> {}", executor.getCorePoolSize(), corePoolSize);
        }

        // 调整最大线程数
        if (maximumPoolSize != executor.getMaximumPoolSize()) {
            executor.setMaximumPoolSize(maximumPoolSize);
            logger.info("动态调整最大线程数:{} -> {}", executor.getMaximumPoolSize(), maximumPoolSize);
        }

        // 调整空闲线程存活时间
        if (keepAliveTime != executor.getKeepAliveTime(TimeUnit.SECONDS)) {
            executor.setKeepAliveTime(keepAliveTime, TimeUnit.SECONDS);
            logger.info("动态调整空闲线程存活时间:{} -> {} 秒", executor.getKeepAliveTime(TimeUnit.SECONDS), keepAliveTime);
        }

        logger.info("线程池参数动态调整完成,当前状态:{}", getThreadPoolStatus(executor));
    }

    private String getThreadPoolStatus(ThreadPoolExecutor executor) {
        return String.format("核心线程数:%d,最大线程数:%d,当前线程数:%d,活跃线程数:%d,任务队列大小:%d,已完成任务数:%d",
                executor.getCorePoolSize(),
                executor.getMaximumPoolSize(),
                executor.getPoolSize(),
                executor.getActiveCount(),
                executor.getQueue().size(),
                executor.getCompletedTaskCount());
    }
}
步骤 3:Nacos 配置文件

在 Nacos 中添加配置:

ini 复制代码
# 线程池配置
thread.pool.corePoolSize=8
thread.pool.maximumPoolSize=16
thread.pool.keepAliveTime=30
thread.pool.queueCapacity=1000

当我们在 Nacos 中修改这些配置时,Spring Cloud 会自动触发 RefreshEvent 事件,我们的监听方法会被调用,从而动态调整线程池参数。

3. 动态调整的注意事项

  1. 核心线程数调整 :当核心线程数调大时,线程池会立即创建新线程;当核心线程数调小时,空闲的核心线程不会被立即销毁,需要等待 keepAliveTime 后才会被销毁(如果设置了 allowCoreThreadTimeOut)。
  2. 最大线程数调整 :最大线程数只能调大,不能调小?不是的,最大线程数可以调小,但已经创建的超过新最大线程数的线程,会在空闲时被销毁。
  3. 队列容量调整 :ThreadPoolExecutor 没有提供 setQueueCapacity 方法,因此队列容量无法动态调整。如果需要动态调整队列容量,可以自定义阻塞队列。
  4. 监控告警:在动态调整线程池参数时,必须监控线程池的状态,当参数调整异常时,及时发送告警。

四、高级开发必备:通用线程池工具类封装

作为一名高级 Java 开发,我在多个项目中封装过线程池工具类,下面是我总结的通用工具类,包含了线程池的创建、动态调整、监控等功能,可直接在生产环境中使用。

1. 线程池枚举类:管理所有线程池

arduino 复制代码
public enum ThreadPoolEnum {

    ORDER_POOL("orderPool", "订单处理线程池", 8, 16, 30, 1000),
    PAY_POOL("payPool", "支付处理线程池", 4, 8, 30, 500),
    LOG_POOL("logPool", "日志处理线程池", 2, 4, 60, 1000);

    /**
     * 线程池名称
     */
    private final String poolName;

    /**
     * 线程池描述
     */
    private final String desc;

    /**
     * 核心线程数
     */
    private final int corePoolSize;

    /**
     * 最大线程数
     */
    private final int maximumPoolSize;

    /**
     * 空闲线程存活时间(秒)
     */
    private final long keepAliveTime;

    /**
     * 队列容量
     */
    private final int queueCapacity;

    ThreadPoolEnum(String poolName, String desc, int corePoolSize, int maximumPoolSize, long keepAliveTime, int queueCapacity) {
        this.poolName = poolName;
        this.desc = desc;
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.keepAliveTime = keepAliveTime;
        this.queueCapacity = queueCapacity;
    }

    // getter 方法
    public String getPoolName() {
        return poolName;
    }

    public String getDesc() {
        return desc;
    }

    public int getCorePoolSize() {
        return corePoolSize;
    }

    public int getMaximumPoolSize() {
        return maximumPoolSize;
    }

    public long getKeepAliveTime() {
        return keepAliveTime;
    }

    public int getQueueCapacity() {
        return queueCapacity;
    }
}

2. 通用线程池工具类

scss 复制代码
public class ThreadPoolUtil {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPoolUtil.class);

    /**
     * 线程池缓存
     */
    private static final Map<String, ThreadPoolExecutor> THREAD_POOL_MAP = new ConcurrentHashMap<>();

    static {
        // 初始化所有线程池
        for (ThreadPoolEnum threadPoolEnum : ThreadPoolEnum.values()) {
            ThreadPoolExecutor executor = createThreadPool(threadPoolEnum);
            THREAD_POOL_MAP.put(threadPoolEnum.getPoolName(), executor);
            logger.info("初始化线程池:{},描述:{}", threadPoolEnum.getPoolName(), threadPoolEnum.getDesc());
        }

        // 注册JVM关闭钩子,优雅关闭线程池
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            logger.info("JVM关闭,开始优雅关闭所有线程池");
            for (Map.Entry<String, ThreadPoolExecutor> entry : THREAD_POOL_MAP.entrySet()) {
                shutdownThreadPool(entry.getKey(), entry.getValue());
            }
        }));
    }

    /**
     * 创建线程池
     */
    private static ThreadPoolExecutor createThreadPool(ThreadPoolEnum threadPoolEnum) {
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat(threadPoolEnum.getPoolName() + "-%d")
                .build();

        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(threadPoolEnum.getQueueCapacity());

        return new ThreadPoolExecutor(
                threadPoolEnum.getCorePoolSize(),
                threadPoolEnum.getMaximumPoolSize(),
                threadPoolEnum.getKeepAliveTime(),
                TimeUnit.SECONDS,
                workQueue,
                threadFactory,
                new CustomRejectedExecutionHandler()
        );
    }

    /**
     * 获取线程池
     */
    public static ThreadPoolExecutor getThreadPool(String poolName) {
        ThreadPoolExecutor executor = THREAD_POOL_MAP.get(poolName);
        if (executor == null) {
            throw new IllegalArgumentException("线程池不存在:" + poolName);
        }
        return executor;
    }

    /**
     * 动态调整线程池参数
     */
    public static void adjustThreadPool(String poolName, int corePoolSize, int maximumPoolSize, long keepAliveTime) {
        ThreadPoolExecutor executor = getThreadPool(poolName);
        if (executor == null) {
            return;
        }

        // 调整核心线程数
        if (corePoolSize > 0 && corePoolSize != executor.getCorePoolSize()) {
            executor.setCorePoolSize(corePoolSize);
            logger.info("动态调整线程池【{}】核心线程数:{} -> {}", poolName, executor.getCorePoolSize(), corePoolSize);
        }

        // 调整最大线程数
        if (maximumPoolSize > 0 && maximumPoolSize != executor.getMaximumPoolSize()) {
            executor.setMaximumPoolSize(maximumPoolSize);
            logger.info("动态调整线程池【{}】最大线程数:{} -> {}", poolName, executor.getMaximumPoolSize(), maximumPoolSize);
        }

        // 调整空闲线程存活时间
        if (keepAliveTime > 0 && keepAliveTime != executor.getKeepAliveTime(TimeUnit.SECONDS)) {
            executor.setKeepAliveTime(keepAliveTime, TimeUnit.SECONDS);
            logger.info("动态调整线程池【{}】空闲线程存活时间:{} -> {} 秒", poolName, executor.getKeepAliveTime(TimeUnit.SECONDS), keepAliveTime);
        }

        logger.info("线程池【{}】参数调整完成,当前状态:{}", poolName, getThreadPoolStatus(executor));
    }

    /**
     * 优雅关闭线程池
     */
    private static void shutdownThreadPool(String poolName, ThreadPoolExecutor executor) {
        if (executor == null || executor.isShutdown()) {
            return;
        }

        logger.info("开始关闭线程池:{},当前状态:{}", poolName, getThreadPoolStatus(executor));

        // 停止接收新任务
        executor.shutdown();

        try {
            // 等待60秒,让已提交的任务执行完成
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                // 超时后,强制关闭线程池
                logger.warn("线程池【{}】关闭超时,强制关闭", poolName);
                List<Runnable> droppedTasks = executor.shutdownNow();
                logger.warn("线程池【{}】强制关闭,丢弃任务数:{}", poolName, droppedTasks.size());
            }
        } catch (InterruptedException e) {
            logger.error("线程池【{}】关闭被中断", poolName, e);
            executor.shutdownNow();
        }

        logger.info("线程池【{}】关闭完成", poolName);
    }

    /**
     * 获取线程池状态
     */
    public static String getThreadPoolStatus(ThreadPoolExecutor executor) {
        return String.format("核心线程数:%d,最大线程数:%d,当前线程数:%d,活跃线程数:%d,任务队列大小:%d,已完成任务数:%d,是否关闭:%s",
                executor.getCorePoolSize(),
                executor.getMaximumPoolSize(),
                executor.getPoolSize(),
                executor.getActiveCount(),
                executor.getQueue().size(),
                executor.getCompletedTaskCount(),
                executor.isShutdown());
    }

    /**
     * 提交任务
     */
    public static void submitTask(String poolName, Runnable task) {
        ThreadPoolExecutor executor = getThreadPool(poolName);
        executor.submit(task);
    }

    /**
     * 提交任务,返回Future
     */
    public static <T> Future<T> submitTask(String poolName, Callable<T> task) {
        ThreadPoolExecutor executor = getThreadPool(poolName);
        return executor.submit(task);
    }
}

3. Spring 上下文工具类

typescript 复制代码
@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }

    /**
     * 获取Bean
     */
    public static <T> T getBean(String beanName, Class<T> requiredType) {
        return applicationContext.getBean(beanName, requiredType);
    }

    /**
     * 获取Bean
     */
    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }
}

五、线程池最佳实践与避坑指南

作为一名八年经验的高级 Java 开发,我总结了以下线程池使用的最佳实践和避坑指南,希望能帮助你在项目中避免踩坑。

1. 最佳实践

  1. 使用有界队列:生产环境必须使用有界队列,避免任务无限堆积导致 OOM。
  2. 自定义线程工厂:设置有意义的线程名称,方便排查问题。
  3. 使用自定义拒绝策略:根据业务需求,自定义拒绝策略,记录日志、发送告警、持久化任务等。
  4. 动态调整线程池参数:结合配置中心,实现线程池参数的动态调整,应对流量波动。
  5. 优雅关闭线程池:注册 JVM 关闭钩子,在应用关闭时,优雅关闭线程池,避免任务丢失。
  6. 监控线程池状态:通过 Spring Boot Actuator 或自定义监控,监控线程池的状态,及时发现问题。
  7. 避免使用 Executors 创建线程池Executors 提供的 newFixedThreadPoolnewCachedThreadPool 等方法,默认使用无界队列,容易导致 OOM。

2. 避坑指南

  1. 坑 1:使用无界队列,导致任务堆积,最终 OOM。

    • 解决方案:使用有界队列,结合拒绝策略一起使用。
  2. 坑 2:核心线程数和最大线程数设置过大,导致 CPU 上下文切换飙升,系统性能下降。

    • 解决方案:根据业务类型(CPU 密集型 / IO 密集型)和压测结果,合理设置核心线程数和最大线程数。
  3. 坑 3:拒绝策略配置不合理,导致核心任务丢失。

    • 解决方案 :核心业务使用 AbortPolicy + 自定义异常处理,非核心业务使用 CallerRunsPolicy 或自定义拒绝策略。
  4. 坑 4:线程池没有优雅关闭,导致应用关闭时任务丢失。

    • 解决方案 :注册 JVM 关闭钩子,在应用关闭时,调用 shutdown() 方法,等待任务执行完成。
  5. 坑 5:动态调整线程池参数时,没有监控线程池状态,导致参数调整异常。

    • 解决方案:在动态调整线程池参数时,记录线程池的状态,并发送告警。

六、总结

线程池是 Java 后端开发中不可或缺的工具,掌握线程池的核心参数、拒绝策略和动态调整技巧,是高级 Java 开发的必备技能。本文从高级 Java 开发的视角,深度解析了线程池的核心参数,详细讲解了 4 种默认拒绝策略和自定义拒绝策略的实战,结合 Nacos 实现了线程池参数的动态调整,并封装了可直接在生产环境中使用的通用线程池工具类。

相关推荐
mjhcsp2 小时前
C++ KMP 算法:原理、实现与应用全解析
java·c++·算法·kmp
邵伯2 小时前
Java源码中的排序算法(一)--Arrays.sort()
java·排序算法
小杨同学492 小时前
C 语言实战:动态规划求解最长公共子串(连续),附完整实现与优化
后端
Cache技术分享2 小时前
290. Java Stream API - 从文本文件的行创建 Stream
前端·后端
用户948357016512 小时前
拒绝 try-catch:如何设计全局通用的异常拦截体系?
后端
golang学习记2 小时前
Go 1.22 隐藏彩蛋:cmp.Or —— 让“默认值”写起来像呼吸一样自然!
后端
阿里巴巴P8高级架构师2 小时前
从0到1:用 Spring Boot 4 + Java 21 打造一个智能AI面试官平台
java·后端
stevenzqzq2 小时前
trace和Get thread dump的区别
java·android studio·断点
桦说编程2 小时前
并发编程踩坑实录:这些原则,帮你少走80%的弯路
java·后端·性能优化