线程池介绍及使用

线程池介绍

  1. 线程池是什么?

    线程池是一种管理和复用线程的机制。它可以在应用程序启动时创建一组线程,并将它们存储在一个池中。当需要执行一个任务时,线程池中的一个线程会被分配给该任务。当任务完成后,该线程会返回到线程池中,可以被复用。

  2. 为什么使用线程池

    当我们需要执行一些比较耗时的任务时,通常会创建新的任务来处理它们。但是,如果我们需要频繁地创建和销毁线程,这会导致很大的开销。这时,线程池就可以派上用场了。

  3. 使用线程池有什么好处

    • 提高性能:由于线程池中的线程可以被复用,因此可以减少线程的创建和销毁开销,从而提高应用程序的性能。

    • 控制资源:线程池可以限制应用程序使用的资源数量。例如,可以限制最大并发线程数,防止过度占用系统资源。

    • 提高可靠性:由于线程池中的线程可以被复用,因此可以减少因频繁创建和销毁线程而导致的内存泄漏和资源竞争等问题。

    • 提高可维护性:使用线程池可以使代码更易于维护和管理。通过统一管理线程池中的线程,可以更好地控制应用程序的行为。

线程池参数介绍

  • corePoolSize

    核心线程数,在没有任务处理时不会被回收的线程数量(如果设置了allow-core-thread-timeout核心线程也会被回收),最小可以设置为0。

  • maximumPoolSize

    最大线程数,线程池能够容纳的最大线程数(不能小于核心线程数),最小可以设置为1。

  • keepAliveTime

    空闲线程最大存活时间,在没有任务处理时,非核心线程能够存活的最大时间,不能小于0。

  • unit

    空闲线程最大存活时间单位,非核心线程能够存活的最大时间单位。

  • workQueue

    任务队列,线程池数量达到核心线程数时存放任务的队列。

    java中提供了四种任务队列,分别是:

    • ArrayBlockingQueue

      基于数组的有界队列。

    • LinkedBlockingQueue

      基于链表的无界队列,严格来说是有界的,容量为 Integer.MAX(),但是实际上任务不可能会达到这个数量,因此设置最大线程数就没有意义。

    • SynchronousQueue

      这是一个没有存储能力的队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。

    • PriorityBlockingQueue

      这是一个基于优先级的队列,可以根据元素的优先级自动对任务进行排序。

  • threadFactory

    线程工厂,用于线程池创建线程的工厂。

  • handler

    拒绝策略,线程池数量达到最大线程数时执行的拒绝策略。

    线程池内部提供了四种拒绝策略,分别是:

    • CallerRunsPolicy

      在达到最大线程数时会在调用 execute 方法的线程中直接执行被拒绝的任务。

    • AbortPolicy

      线程池默认的拒绝策略,丢弃任务并抛出异常。

    • DiscardPolicy

      直接丢弃任务。

    • DiscardOldestPolicy

      分析源码

      // 获取线程池的工作队列并调用poll()方法来移除队列中最早的任务
      e.getQueue().poll();
      // 将新任务添加到工作队列中。如果此时线程池中有空闲线程,则该线程会立即执行该任务;否则该任务会等待空闲线程来执行。
      e.execute(r);
      

线程池工作原理

​ 当我们向线程池提交任务时,首先判断线程池中的线程数是否达到核心线程数,如果没有达到核心线程数就创建一个新的线程执行任务,否则放入任务队列中等待执行。如果任务队列已满就判断是否达到最大线程数,如果还未达到最大线程数则创建新的线程,若达到最大线程数则执行拒绝策略。

在这里插入图片描述

打个比方,线程池好比一个大户人家,核心线程就是大户人家常驻的工人,开始有活的时候就要开始招工人了,活开始慢慢来,招工到了一定数量(corePoolSize)时,人手不够可以暂时将活放在一边(workQueue),这样设计的道理是,不可能一直频繁招工,会有一定开销,并且任务也不会一直这样多,等到不能容忍一定的任务未处理时就再陆续招人手,如果人手已经到一定程度了(maximumPoolSize)就会拒绝任务(handler),当没有活干,人手空闲下来时,除了常驻的人手,另外的人手就会在一定时间(keepAliveTime)没有活干后被遣散。线程池的目的就在于提供这样一个缓冲作用。

线程池使用

  1. 创建ThreadPoolExecutor对象

    ThreadPoolExecutor类为我们提供了四个构造方法,corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue为必填参数,threadFactory、handler可以都提供、都不提供或者提供任意一个,不提供的话会使用线程池默认的线程工厂和拒绝策略。

    在实际开发中,线程池创建的参数要根据业务来灵活设置

    为方便测试,我们定义核心线程数为2,最大线程数为3,最大空闲时间3min,并使用容量为1的任务队列,拒绝策略使用默认的拒绝策略AbortPolicy

    ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3, 1, TimeUnit.MINUTES, new ArrayBlockingQueue(1));
    
  2. 提交任务

    @GetMapping("/threadTest")
        public void threadTest() {
            // 区分每个线程
            long l = System.currentTimeMillis();
            System.out.println(l + "->" + executor.getPoolSize());
            
            // 使用创建的线程池对象来提交任务
            executor.submit(() -> {
                System.out.println(l + "->" + executor.getPoolSize());
                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(l + "----------------->" + executor.getPoolSize());
            });
        }
    
  3. 输出结果分析

在这里插入图片描述

可以看到我们连续向线程池提交5个任务的执行结果,我们在向线程池提交任务之前获取了线程池的线程数,并在提交任务之后也获取了,目的是观测线程的创建,分析上述打印结果,提交第一个任务之前线程池是0个线程,而提交任务之后是1个,这里线程池创建了一个线程来处理任务。第二个任务提交之前是1个,提交之后是2个,也是由于线程池创建了线程来处理任务。第三个任务只能看到提交之前是2个线程,但是没有打印提交之后的线程数是因为此时线程数达到核心线程数,线程池并没有创建新的线程,而是向任务队列提交了任务等待执行。第四个任务由于已经达到核心线程数,并且任务队列容量我们设置为1,并且在我们存放了第三个任务已经没有了空余,所以创建了新的线程来执行,可以看到提交第三个任务之后线程池线程数量为3。这时我们再提交第五个任务,由于已经达到最大线程数,所以线程池会执行我们定义的拒绝策略,拒绝任务,并且抛出异常。第一个任务执行完后,任务队列的任务开始执行。

预估线程回收之后我们又提交任务

在这里插入图片描述

可以看到提交任务之前和之后都是两个线程,是由于非核心线程已被回收,核心线程仍然存在,提交任务直接复用了存在的线程,减少了线程创建的开销。

总结

线程池是一种常用的多线程编程技术,它可以在多线程环境下提高系统性能、资源利用率、响应速度和代码可维护性。线程池的设计可以避免频繁地创建和销毁线程,从而减少了系统开销,提高了系统性能;可以控制同时运行的线程数量,从而避免因为过多的线程而导致系统资源不足的问题;可以避免因为线程创建和销毁的时间而导致的延迟,从而提高了系统的响应速度;可以将线程管理的代码与业务逻辑分离,使得代码更加清晰易懂,易于维护。需要注意的是,在使用线程池时需要根据具体情况选择合适的线程池大小,并且需要注意线程安全问题,以免出现意外错误

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值