天不生线程池,Java 万古如长夜!

天不生线程池,Java 万古如长夜!

0 概述

  • Java 中的线程池是一种重要的线程管理机制,通过线程池可以对线程进行有效的管理和控制,提高程序的性能和可靠性。线程池可以减少线程创建和销毁的开销,通过对线程的重复利用,可以降低系统负载,并且在大量的并发访问情况下,可以有效地保证系统的稳定性。

1 线程池的优缺点

  • 使用线程池的优点:
    • 减少线程创建和销毁的开销:线程的创建和销毁需要耗费大量的系统资源,使用线程池可以减少这些开销,通过对线程的重复利用可以降低系统负载
    • 提高程序的性能和可靠性:线程池可以有效地控制线程的数量,避免了过多线程的竞争和冲突,提高了程序的性能和可靠性。
    • 优化系统资源的利用:线程池可以根据实际情况自动调整线程数,充分利用系统资源,避免了资源的浪费
  • 使用线程池的缺点:
    • 线程池的创建和配置需要一定的技术水平:在不了解线程池的使用和配置参数的情况下,可能会导致线程池的使用效果不佳或者出现错误
    • 线程池可能会占用较多的内存空间:线程池需要维护一个等待队列和多个线程对象,可能会占用较多的内存空间

2 使用和不使用线程池的对比

  • 使用线程池:
    • 可以减少线程创建和销毁的开销,优化系统资源的利用
    • 可以提高程序的性能和可靠性,避免了过多线程的竞争和冲突
    • 可以自动调整线程数,适应不同的任务需求
  • 不使用线程池:
    • 需要手动管理线程的创建和销毁,增加了程序的复杂性和难度
    • 容易产生过多的线程,导致系统资源的浪费和负载的增加
    • 可能会出现线程竞争和冲突等问题,影响程序的性能和可靠性

3 内置的线程池

  • Java 内置的线程池主要有以下几个:
    • FixedThreadPool(固定大小线程池):其核心线程数和最大线程数都是固定的,当任务提交到线程池中时,如果有空闲的线程,则立即执行任务,否则将任务加入等待队列中,等待空闲的线程执行
    • CachedThreadPool(缓存线程池):是一种无限制大小的线程池,它会根据任务的数量自动调整线程池的大小,当需要执行任务时,如果有空闲的线程,则立即执行任务,否则会创建一个新的线程来执行任务
    • SingleThreadExecutor(单线程线程池):只有一个线程的线程池,所有任务都在同一个线程中顺序执行,保证了任务的有序性和可控性
    • ScheduledThreadPool(定时任务线程池):可以用于执行定时任务或周期性任务,在指定的时间间隔内执行任务,并且可以设置任务的延迟时间和执行周期
  • 通过 Executors工具类调用对应的静态方法进行创建
    // 固定大小线程池, 其中 n 为线程池中的线程数
    // [注意] corePoolSize 和 maxPoolSize 都设置成了指定的线程数量,即 corePoolSize == maxPoolSize
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(n);
    
    // 缓存线程池
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    
    // 单线程线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    
    // 定时任务线程池,其中 n 为要保留在池中的线程数,即核心线程数
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(n);
    
  • 这四个方法的源码如下:
    public static ExecutorService newFixedThreadPool(int nThreads) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
       super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
    }
    
  • 补充说明:《阿里巴巴开发规范手册》对使用线程池有明确规定,原文如下:
    1、【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
    2、【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
    3、【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    	说明:Executors 返回的线程池对象的弊端如下:
    		1)FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    		2)CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
    

4 自定义线程池

  • 我们也可以通过自定义线程池来创建符合我们需求的线程池。自定义线程池需要设置以下 7 大核心参数

    • corePoolSize:线程池的核心线程数,当任务提交到线程池中时,如果当前线程数小于corePoolSize,则会立即创建新的线程来执行任务,否则将任务加入等待队列中
    • maximumPoolSize:线程池允许的最大线程数,当等待队列已满且当前线程数小于 maximumPoolSize 时,会创建新的线程来执行任务,超过 maximumPoolSize 的任务将被拒绝
    • keepAliveTime:线程的存活时间,当线程空闲时间超过 keepAliveTime 时,线程会被销毁,直到线程数不超过 corePoolSize
    • unit:存活时间的单位,常见的单位有秒、毫秒、微秒等
    • workQueue:等待队列,用于存放还未执行的任务,线程池中有多种类型的等待队列可供选择,如 SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue 等
    • threadFactory:线程工厂,可选参数,它指定线程池创建线程的方式,通常使用默认的线程工厂
    • handler:拒绝策略,可选参数,它定义了当线程池中线程数达到最大线程数且任务队列已满时,应该如何处理新提交的任务。常见的拒绝策略包括丢弃任务或者重新安排到一个不同的线程池中
  • 下面是一个简单的自定义线程池 demo,模拟任务执行:

    import java.util.concurrent.*;
    
    /**
    * @Author Jasper
    * @Time 2021/02/01
    */
    public class ThreadPoolDemo {
    
        public final static ThreadPoolExecutor CUSTOMER_EXECUTOR = new ThreadPoolExecutor(
                3,
                5,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    
        public static void main(String[] args) {
            // 提交任务
            for (int i = 0; i < 10; i++) {
                final int taskId = i;
                CUSTOMER_EXECUTOR.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId);
                    try {
                        Thread.sleep(1000); // 模拟任务执行时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
            // 关闭线程池
            CUSTOMER_EXECUTOR.shutdown();
        }
    }
    /**
    *  输出:
    * 	pool-1-thread-1 执行任务 0
    *	pool-1-thread-2 执行任务 1
    *	pool-1-thread-3 执行任务 2
    *	pool-1-thread-3 执行任务 3
    *	pool-1-thread-1 执行任务 4
    *	pool-1-thread-2 执行任务 5
    *	pool-1-thread-2 执行任务 6
    *	pool-1-thread-3 执行任务 8
    *	pool-1-thread-1 执行任务 7
    *	pool-1-thread-2 执行任务 9
    */
    
  • 另一种创建自定义线程池的方式:SpringBoot 配置文件注入 Bean 对象

    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.ThreadPoolExecutor;
    
    /**
     * @Author Jasper
     * @Time 2024/02/01
     */
    @Configuration
    @EnableAsync
    public class CustomerExecutorConfig {
    
        /**
         * 核心线程数(默认线程数)
         */
        private static final int CORE_POOL_SIZE = 5;
    
        /**
         * 最大线程数
         */
        private static final int MAX_POOL_SIZE = 20;
    
        /**
         * 允许线程空闲时间(单位:默认为秒)
         */
        private static final int KEEP_ALIVE_TIME = 10;
    
        /**
         * 缓冲队列大小
         */
        private static final int QUEUE_CAPACITY = 200;
    
        /**
         * 线程池名前缀
         */
        private static final String THREAD_NAME_PREFIX = "Jasper-Async-";
    
        @Bean("customerExecutor")
        public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
            ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
            // 设置相关参数
            threadPoolTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);
            threadPoolTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
            threadPoolTaskExecutor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
            threadPoolTaskExecutor.setQueueCapacity(QUEUE_CAPACITY);
            threadPoolTaskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
            threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
            // 初始化
            threadPoolTaskExecutor.initialize();
            return threadPoolTaskExecutor;
        }
    }
    
  • 编写模拟任务的代码

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author Jasper
     * @Time 2024/02/01
     */
    @Component
    @Slf4j
    public class AsyncTask {
    
        @Async("customerExecutor")
        public void doTask() {
            log.info("开始做任务...");
            long start = System.currentTimeMillis();
            try {
            	// 模拟任务耗时
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                log.error("异常:{}", e.getMessage(), e);
            }
            long end = System.currentTimeMillis();
            log.info("完成任务,耗时:" + (end - start) + "毫秒");
        }
    
        public String priorityTask() {
            Thread.currentThread().setPriority(1);
            return "高优先级任务,id:" + Thread.currentThread().getId() + " 由 " + Thread.currentThread().getName() + " 执行";
        }
    }
    
  • Controller 层:

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import javax.annotation.Resource;
    
    /**
     * @Author Jasper
     * @Time 2024/02/01
     */
    @RestController
    public class AsyncController {
    
        @Resource
        private AsyncTask asyncTask;
    
        @GetMapping("/myAsyncTask")
        public void test() {
            asyncTask.doTask();
            System.out.println(asyncTask.priorityTask());
        }
    
    }
    
  • 启动服务,访问测试:localhost:9999/myAsyncTask

    输出:
    高优先级任务,id:34 由 http-nio-9999-exec-1 执行
    2024-01-31 09:55:40.794  INFO 17676 --- [ Jasper-Async-1] c.example.demo.threadCoustom.AsyncTask   : 开始做任务...
    2024-01-31 09:55:42.794  INFO 17676 --- [ Jasper-Async-1] c.example.demo.threadCoustom.AsyncTask   : 完成任务,耗时:2000毫秒
    
  • 可以看到,使用了指定的自定义线程池的任务,就会由自定义的线程池去执行任务,而没有指定的就由默认线程去执行任务

  • 总结:线程池是 Java 中重要的线程管理机制,通过线程池可以对线程进行有效的管理和控制,提高程序的性能和可靠性。Java 中内置了多种类型的线程池,同时也支持自定义线程池,通过设置不同的参数,可以创建符合需求的线程池。使用线程池可以减少线程创建和销毁的开销,提高程序的性能和可靠性,同时也需要注意线程池的配置和使用方式,以充分发挥其优势。

  • 以上就是线程池的相关内容了,小伙伴们在真实的场景中是怎么使用线程池的呢?可以在评论区讨论哦~

  • 创作不易,感谢阅读,若遇到问题,可以关注此微信gzh:EzCoding 留言反馈,希望能够帮助到您

你可能感兴趣的:(java,后端,spring,boot)