浅谈线程池

一.线程池的作用

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
  3. 提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

二.java线程池ThreadPoolExecutor类使用详解
       在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

1.ThreadPoolExecutor类的构造方法以及构造函数的参数含义

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;

maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;

keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;

unit:keepAliveTime的单位;

workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;

threadFactory:线程工厂,用于创建线程,一般用默认即可(java8中的构造方法使用默认形式);

handler:拒绝策略;当任务太多来不及处理时,执行拒绝任务。

2.workQueue任务队列:

(1)、直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量, 每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。

特性:当任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;

public class ThreadPool {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
        pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, 
               new SynchronousQueue<Runnable>(),
               Executors.defaultThreadFactory(),
               new ThreadPoolExecutor.AbortPolicy());
        for(int i=0;i<3;i++) {
            readPool.execute(new ThreadTask());
        }   
    }
}
public class ThreadTask implements Runnable{
    public ThreadTask() {      
    }
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

(2)、有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现

特性:使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, 
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

(3)、无界的任务队列:无界任务队列可以使用LinkedBlockingQueue实现

特性:使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, 
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

(4)、优先任务队列:优先任务队列通过PriorityBlockingQueue实现

特性:PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, 
new PriorityBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

3.拒绝策略

一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下:

(1)、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;

(2)、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;

(3)、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;

(4)、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失

三.Spring Boot创建线程池

@Configuration
@EnableAsync
public class ThreadPoolConfig {
    @Bean
    public TaskExecutor asyncServiceExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor ();
        // 设置核心线程数
        // 指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去
        executor.setCorePoolSize(12);
        // 设置最大线程数
        // 指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量
        executor.setMaxPoolSize(12);
        // 设置队列容量
        // new LinkedBlockingQueue<Runnable>();
        executor.setQueueCapacity(32);
        // 设置线程活跃时间(秒)
        // 当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁
        //executor.setKeepAliveSeconds(300);
        // 设置默认线程名称
        executor.setThreadNamePrefix("async-thread-");
        // 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务 CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        // executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
}

四.线程池线程数量

线程吃线程数量的设置没有一个明确的指标,根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可

/**
 * Nthreads=CPU数量
 * Ucpu=目标CPU的使用率,0<=Ucpu<=1
 * W/C=任务等待时间与任务计算时间的比率
 */
 Nthreads = Ncpu*Ucpu*(1+W/C)

### Spring Boot框架简介与使用方法 Spring Boot 是由 Pivotal 团队提供的一个开源框架,旨在简化基于 Spring 框架的项目开发过程。它遵循“约定大于配置”的设计原则,减少了开发者在配置上的工作量[^2]。通过内置的默认配置和自动化配置机制,Spring Boot 提供了快速构建独立、生产级别的 Spring 应用程序的能力。 #### 1. Spring Boot 的核心概念 Spring Boot 的核心理念是减少开发者的负担,提供开箱即用的功能。以下是 Spring Boot 的几个关键特性: - **自动化配置**:Spring Boot 根据类路径中的依赖项自动配置 Spring 应用程序。例如,当引入数据库驱动依赖时,Spring Boot 会自动配置数据源和相关的 Bean 对象[^2]。 - **嵌入式服务器**:Spring Boot 内置了 Tomcat、Jetty 或 Undertow 等 Web 容器,使得应用程序可以独立运行而无需外部部署环境。 - **起步依赖(Starters)**:Spring Boot 提供了一系列的 Starter 模块,这些模块封装了常用的依赖和配置,方便开发者快速集成各种功能。例如,`spring-boot-starter-web` 提供了构建 Web 应用所需的所有依赖[^2]。 - **监控支持**:通过引入 `spring-boot-starter-actuator` 依赖,开发者可以获得丰富的监控指标,包括应用健康状态、线程池信息、内存使用情况等[^1]。 #### 2. Spring Boot 的执行流程 Spring Boot 的主要执行流程可以分为以下几个阶段: - **加载配置**:Spring Boot 从多个来源(如 `application.properties` 或 `application.yml` 文件)加载配置属性,并将其注入到应用程序中。 - **自动化配置**:根据类路径中的依赖项和配置属性,Spring Boot 自动配置 Spring 上下文。 - **启动嵌入式容器**:如果应用程序是一个 Web 应用,Spring Boot 将启动嵌入式 Web 容器。 - **运行应用程序**:完成上述步骤后,Spring Boot 启动应用程序并开始处理请求[^1]。 #### 3. 创建一个简单的 Spring Boot 项目 以下是创建和运行一个简单 Spring Boot 项目的步骤: 1. **添加依赖** 在 `pom.xml` 文件中添加 Spring Boot 的依赖项: ```xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> ``` 2. **编写主类** 创建一个包含 `@SpringBootApplication` 注解的 Java 类: ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 3. **创建控制器** 添加一个 REST 控制器来处理 HTTP 请求: ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String sayHello() { return "Hello, Spring Boot!"; } } ``` 4. **运行项目** 使用以下命令运行项目: ```bash mvn spring-boot:run ``` 访问 `http://localhost:8080/hello` 即可看到输出结果。 #### 4. Spring Boot 的优势 Spring Boot 提供了诸多优点,使其成为现代 Java 开发的首选框架之一: - **快速集成**:通过 Starter 模块,开发者可以秒级集成各种框架[^2]。 - **减少配置**:完全抛弃繁琐的 XML 配置,采用注解和配置文件的方式进行开发[^2]。 - **内置监控**:通过 Actuator 模块,开发者可以获取丰富的监控指标[^1]。 - **易于扩展**:虽然 Spring Boot 本身是一个微框架,但它可以通过 Spring Cloud 等工具扩展以支持服务发现、注册、负载均衡等功能[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值