java线程池

问java线程池解决上面问题,常见使用场景是什么

这就触及到了线程池最核心的"自我保护"机制!当线程池里的所有线程(包括核心线程和临时线程)都在忙,并且排队的任务也塞满时,线程池就会启动最后一道防线------拒绝策略

简单来说,当"并发量太大,实在处理不过来"时,Java 线程池提供了 4 种标准的应对方式(也就是 4 种拒绝策略),你可以根据业务的重要程度来选择:

🛑 1. 直接报错 (AbortPolicy) ------ 这是默认策略

  • 处理方式:直接抛出一个异常,告诉提交任务的人:"别发了,我这边已经爆了!"
  • 适用场景 :适用于那些绝对不能丢、也不能拖的关键业务。比如支付扣款,如果系统处理不过来,必须立刻让前端知道失败了,以便提示用户重试,而不是默默把任务丢掉。

🏃 2. 让提交者自己干 (CallerRunsPolicy) ------ 最推荐的"削峰"策略

  • 处理方式 :线程池虽然忙,但它不拒绝任务。它会把新来的任务"退回去",让提交这个任务的主线程自己去执行这个任务
  • 适用场景 :这是一种非常聪明的**"反压(Backpressure)"机制**。比如你的 Web 服务器(Tomcat)线程把任务丢给线程池,线程池满了,Tomcat 线程只能自己去干活。这样一来,Tomcat 线程忙于执行任务,就没空接收新的 HTTP 请求了,相当于自动给系统降速,防止洪峰流量把服务器彻底冲垮

🗑️ 3. 悄悄丢弃任务 (DiscardPolicy)

  • 处理方式:新来的任务直接被扔掉,不抛异常,也不做任何处理,就像没发生过一样。
  • 适用场景 :适用于完全不重要、丢了也无所谓的业务。比如你在后台记录一些非核心的用户行为日志、点击流数据等,高并发时丢掉一部分对整体业务没有任何影响。

🔄 4. 丢弃最老的任务 (DiscardOldestPolicy)

  • 处理方式 :把任务队列里排队时间最长(最旧)的那个任务踢掉,然后尝试把当前这个新任务塞进队列里。
  • 适用场景:适用于**"喜新厌旧"**的业务。比如实时刷新股票行情、最新的系统监控数据等,我们只关心最新的状态,旧的数据早就过时了,丢了反而更好。

📌 结合你的学生管理系统来理解

假设你的系统里有一个"批量给学生发送邮件"的功能,使用了线程池来处理:

  • 如果用默认策略(直接报错):期末考试出分,几千个家长同时查分触发邮件发送。线程池满了之后,后面的查分请求直接报错,家长看到页面提示"系统异常"。
  • 如果用 CallerRunsPolicy(让提交者自己干) :线程池满了,Web 服务器被迫自己去发邮件。虽然发得慢,网页加载变卡了,但没有任何一个家长的查分请求会失败,系统扛过了高峰期。
  • 如果用 DiscardPolicy(悄悄丢弃):线程池满了,后面几百个家长的邮件直接没发出去。家长查分成功了,但死活收不到邮件,开始疯狂投诉(这种场景绝对不能用丢弃策略)。

💡 进阶提示:

在实际大厂开发中,除了这 4 种内置策略,我们经常会自定义拒绝策略 。比如当线程池满了,不直接丢弃,而是把任务写入到数据库或者消息队列(如 Kafka、RabbitMQ)里,等系统空闲了,再通过定时任务把这些积压的任务重新捞出来处理,从而实现数据的"最终一致性"。

问那比如分了多个线程还不够,那其余并发怎么处理

这就触及到了线程池最核心的"自我保护"机制!当线程池里的所有线程(包括核心线程和临时线程)都在忙,并且排队的任务也塞满时,线程池就会启动最后一道防线------拒绝策略

简单来说,当"并发量太大,实在处理不过来"时,Java 线程池提供了 4 种标准的应对方式(也就是 4 种拒绝策略),你可以根据业务的重要程度来选择:

🛑 1. 直接报错 (AbortPolicy) ------ 这是默认策略

  • 处理方式:直接抛出一个异常,告诉提交任务的人:"别发了,我这边已经爆了!"
  • 适用场景 :适用于那些绝对不能丢、也不能拖的关键业务。比如支付扣款,如果系统处理不过来,必须立刻让前端知道失败了,以便提示用户重试,而不是默默把任务丢掉。

🏃 2. 让提交者自己干 (CallerRunsPolicy) ------ 最推荐的"削峰"策略

  • 处理方式 :线程池虽然忙,但它不拒绝任务。它会把新来的任务"退回去",让提交这个任务的主线程自己去执行这个任务
  • 适用场景 :这是一种非常聪明的**"反压(Backpressure)"机制**。比如你的 Web 服务器(Tomcat)线程把任务丢给线程池,线程池满了,Tomcat 线程只能自己去干活。这样一来,Tomcat 线程忙于执行任务,就没空接收新的 HTTP 请求了,相当于自动给系统降速,防止洪峰流量把服务器彻底冲垮

🗑️ 3. 悄悄丢弃任务 (DiscardPolicy)

  • 处理方式:新来的任务直接被扔掉,不抛异常,也不做任何处理,就像没发生过一样。
  • 适用场景 :适用于完全不重要、丢了也无所谓的业务。比如你在后台记录一些非核心的用户行为日志、点击流数据等,高并发时丢掉一部分对整体业务没有任何影响。

🔄 4. 丢弃最老的任务 (DiscardOldestPolicy)

  • 处理方式 :把任务队列里排队时间最长(最旧)的那个任务踢掉,然后尝试把当前这个新任务塞进队列里。
  • 适用场景:适用于**"喜新厌旧"**的业务。比如实时刷新股票行情、最新的系统监控数据等,我们只关心最新的状态,旧的数据早就过时了,丢了反而更好。

📌 结合你的学生管理系统来理解

假设你的系统里有一个"批量给学生发送邮件"的功能,使用了线程池来处理:

  • 如果用默认策略(直接报错):期末考试出分,几千个家长同时查分触发邮件发送。线程池满了之后,后面的查分请求直接报错,家长看到页面提示"系统异常"。
  • 如果用 CallerRunsPolicy(让提交者自己干) :线程池满了,Web 服务器被迫自己去发邮件。虽然发得慢,网页加载变卡了,但没有任何一个家长的查分请求会失败,系统扛过了高峰期。
  • 如果用 DiscardPolicy(悄悄丢弃):线程池满了,后面几百个家长的邮件直接没发出去。家长查分成功了,但死活收不到邮件,开始疯狂投诉(这种场景绝对不能用丢弃策略)。

💡 进阶提示:

在实际大厂开发中,除了这 4 种内置策略,我们经常会自定义拒绝策略 。比如当线程池满了,不直接丢弃,而是把任务写入到数据库或者消息队列(如 Kafka、RabbitMQ)里,等系统空闲了,再通过定时任务把这些积压的任务重新捞出来处理,从而实现数据的"最终一致性"。

问那怎么引入线程池。具体代码怎么写,什么步骤,技术栈还是spring-boot + mybatis-plus

在 Spring Boot + MyBatis-Plus 技术栈中引入线程池,最优雅且标准的方式是结合 Spring 的 @Async 注解来实现。

整个过程分为 4 个核心步骤,我为你整理了最简洁、最不容易踩坑的代码写法:

🛠️ 步骤一:创建线程池配置类(核心)

在项目中创建一个配置类,用来定义你的线程池。切记:千万不要直接裸用 @Async,必须自定义线程池,否则高并发下会无限创建线程导致服务器崩溃。

java 复制代码
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync // 1. 开启异步任务支持
public class ThreadPoolConfig {

    @Bean("myTaskExecutor") // 2. 给线程池起个名字,方便后面指定使用
    public Executor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数(常驻干活的线程)
        executor.setCorePoolSize(5); 
        // 最大线程数(高峰期最多招多少人)
        executor.setMaxPoolSize(10); 
        // 任务队列容量(候客区能坐多少人)
        executor.setQueueCapacity(100); 
        // 线程空闲存活时间(秒)
        executor.setKeepAliveSeconds(60); 
        // 线程名字前缀(方便排查日志时知道是哪个线程)
        executor.setThreadNamePrefix("MyAsync-"); 
        // 拒绝策略:队列和线程都满了,让提交任务的主线程自己去执行(削峰保命)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 
        executor.initialize();
        return executor;
    }
}

⚙️ 步骤二:在启动类上开启异步支持

在你的 Spring Boot 主启动类上加上 @EnableAsync 注解(如果你已经在配置类上加了,这里可以不加,但加上是个好习惯)。

java 复制代码
@SpringBootApplication
@EnableAsync
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

💼 步骤三:在 Service 层编写异步业务方法

把那些耗时的、需要异步执行的任务(比如批量插入数据、发邮件等)单独写在一个方法里,并打上 @Async 标签,指定使用刚才配置的线程池。

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.Student;
import com.example.demo.mapper.StudentMapper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class StudentService {

    @Resource
    private StudentMapper studentMapper;

    // 指定使用名为 "myTaskExecutor" 的线程池来执行这个方法
    @Async("myTaskExecutor") 
    public void batchInsertStudents(List<Student> studentList) {
        // 这里写耗时的数据库操作,MyBatis-Plus 的批量插入
        for (Student student : studentList) {
            studentMapper.insert(student);
        }
        System.out.println("线程[" + Thread.currentThread().getName() + "] 批量插入完成!");
    }
}

🚀 步骤四:在 Controller 或其他地方调用

直接像调用普通方法一样去调用它即可。Spring 会自动把这个任务丢给线程池去后台执行,主线程会立刻继续往下走,不会被卡住。

java 复制代码
package com.example.demo.controller;

import com.example.demo.entity.Student;
import com.example.demo.service.StudentService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@RestController
public class StudentController {

    @Resource
    private StudentService studentService;

    @PostMapping("/import-students")
    public String importStudents() {
        // 模拟一堆学生数据
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Student s = new Student();
            s.setName("学生" + i);
            students.add(s);
        }

        // 调用异步方法(主线程发完任务立刻返回,不等它插完数据库)
        studentService.batchInsertStudents(students);
        
        return "数据导入任务已提交到后台处理,请稍后查看!";
    }
}

⚠️ 新手避坑指南(非常重要)

  1. 同类调用失效@Async 注解是基于 Spring AOP(动态代理)实现的。不能在同一个类里面直接调用加了 @Async 的方法,否则注解会失效,变成同步执行。一定要在 Controller 里注入 Service 来调用,或者通过注入自身 Bean 的方式调用。
  2. 数据库连接池大小 :使用了线程池后,并发量会变大。记得去 application.yml 里检查一下你的数据库连接池(如 HikariCP)的 maximum-pool-size。如果线程池最大有 10 个线程在干活,但数据库连接池只有 5 个连接,线程就会因为抢不到数据库连接而阻塞。
  3. 方法必须是 public :加了 @Async 的方法必须是 public 修饰的,否则无法被代理。

按照这四步走,你就成功在 Spring Boot 项目中引入了一个安全、可控的线程池!

问这个线程池,是对所有项目所有接口起作用是吗?

不是的 。我们刚才配置的这个线程池,只对明确使用了 @Async 注解的方法起作用,绝对不会影响到你项目里所有的接口和请求。

为了让你更清晰地理解,我们可以从以下三个维度来看:

1. 它只管"被标记"的异步任务

这个线程池就像一个**"特种部队"**。

  • 你项目里绝大多数的普通接口(比如简单的增删改查、页面跳转),依然走的是 Spring Boot 默认的 Web 服务器(比如 Tomcat)的普通线程,完全不会经过这个线程池
  • 只有当你给某个方法打上 @Async 标签,并且指定了 @Async("myTaskExecutor") 时,这个方法才会被丢进这个"特种部队"的营地里去执行。

2. 不同的线程池,分工不同

在一个标准的 Spring Boot 项目中,其实同时运行着好几套线程池,它们各司其职:

  • Tomcat 线程池(默认):负责接待所有的 HTTP 请求(比如用户打开网页、点击按钮)。这是项目的主线程池,我们自定义的线程池不会干扰它。
  • 自定义的 myTaskExecutor(我们刚写的):专门用来处理你指定的耗时后台任务(比如发邮件、批量导数据)。
  • 数据库连接池(HikariCP):专门负责和数据库打交道。

所以,你自定义的线程池只是项目里的一个"小部门",专门处理脏活累活,不会影响"大部队"(普通接口)的正常运转。

3. 甚至可以配置多个线程池

如果以后你的项目变大了,你还可以创建多个线程池来分别处理不同的业务。

比如:

  • 创建一个 emailExecutor 专门发邮件。
  • 创建一个 reportExecutor 专门生成报表。

你只需要在配置类里多写几个 @Bean,然后在不同的 Service 方法上通过 @Async("emailExecutor")@Async("reportExecutor") 来指定即可。它们之间互不干扰,非常灵活。

总结一下:

你刚才写的线程池配置,只对那些你主动加了 @Async("myTaskExecutor") 的方法生效。其他的普通接口依然按照原来的方式运行,完全不受影响,你可以放一百个心!

相关推荐
网络工程小王2 小时前
【LCEL 链式调用详解】调用篇-2
java·服务器·前端·数据库·人工智能
Zzzzmo_2 小时前
【JavaEE】文件操作和IO
java·java-ee·io·文件操作·file·流对象
NE_STOP2 小时前
Redis-持久化之RDB
java
苍煜3 小时前
SpringBoot AOP切面编程精讲:实现方式、Spring区别及与自定义注解生产实战
java·spring boot·spring
胡小禾3 小时前
企业内部文件处理方案
java
无限进步_3 小时前
二叉搜索树完全解析:从概念到实现与应用场景
c语言·开发语言·数据结构·c++·算法·github·visual studio
常利兵3 小时前
Spring Boot:别再重复造轮子,这些内置功能香麻了
java·spring boot·后端
努力努力再努力FFF3 小时前
别再乱学PS、Python了,普通大学生该看懂的技能趋势
开发语言·python
天若有情6734 小时前
逆向玩家狂喜!用C++野生写法一键破解线性加密(不规范但巨好用)
开发语言·c++·算法