超详细的线程池讲解!!!

线程池(Thread Pool)是一种基于池化技术设计用于管理线程生命周期的框架或模式。它允许开发者创建一组工作线程,这些线程可以反复被用来执行不同的任务,而不是每次执行任务时都创建和销毁线程。

线程池的主要目的是减少线程创建和销毁的开销,提高系统的响应速度和吞吐量,同时限制系统中并发线程的数量,避免过多的线程导致系统资源耗尽。

线程池的主要优点:

  1. 降低资源消耗:通过重用已存在的线程,减少线程创建和销毁的开销。
  2. 提高响应速度:当任务到达时,可以直接复用线程池中的线程,而不需要等待新线程的创建。
  3. 提高线程的可管理性:线程池可以统一管理线程,包括线程的创建、调度、执行、销毁等,简化了并发编程的复杂性。

线程池的实现:

通过Executors执行器自动创建线程池
  1. newFixedThreadPool(int nThreads)
    • 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
    • 使用无界的工作队列,任何时候最多有nThreads个工作线程是活动的。
  2. newCachedThreadPool()
    • 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
    • 内部使用SynchronousQueue作为工作队列,适合处理大量短时间工作任务的场景。
  3. newSingleThreadExecutor()
    • 创建一个单线程的线程池,它可以保证所有任务都是顺序执行的,最多会有一个任务处于活动状态。
    • 操作一个无界的工作队列,且不允许使用者改动线程池实例,因此可以避免改变线程数目。
  4. newScheduledThreadPool(int corePoolSize)
    • 创建一个可以执行延迟任务的线程池,即支持定时或周期性执行任务的线程池。
    • 核心线程池的大小为corePoolSize,可以根据需要调整。
  5. newSingleThreadScheduledExecutor()
    • 创建一个单线程的可以执行延迟任务的线程池,与newScheduledThreadPool类似,但限制了线程池中的线程数为1。
  6. newWorkStealingPool(int parallelism)
    • Java 8中加入的线程池创建方法,内部会构建ForkJoinPool,利用Work-Stealing算法并行地处理任务,但不保证处理顺序。
通过ThreadPoolExecutor手动创建线程池
  • ThreadPoolExecutor是线程池的核心类,它提供了丰富的参数配置,可以手动创建具有特定特性的线程池。
  • 它最多可以设置7个参数,但最少可以设置5个参数。这些参数包括核心线程数、最大线程数、空闲线程存活时间、时间单位、工作队列以及线程工厂和拒绝策略等。

线程池的关键参数:

  • 核心线程数(corePoolSize):线程池中的核心线程数,即使这些线程处于空闲状态,线程池也不会回收它们。
  • 最大线程数(maximumPoolSize):线程池中允许的最大线程数,也就是总线程数
  • 救急线程空闲存活时间(keepAliveTime):当线程数大于核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间。(如果核心线程数=总线程数,那么就没有救急线程,否则救急线程数=总线程数-核心线程数)
  • 时间单位(unit):keepAliveTime参数的时间单位,如秒、分等。
  • 工作队列(workQueue):用于存放待执行的任务的阻塞队列。可以使用BlockingQueue接口的任何实现,常见的有:
    • ArrayBlockingQueue
      • 一个由数组支持的有界阻塞队列。按照先进先出(FIFO)的原则对元素进行排序。
      • 可限制线程池中同时执行的任务数量。
    • LinkedBlockingQueue
      • 一个由链表结构组成的可选有界阻塞队列。如果构造时未指定容量,则默认容量为Integer.MAX_VALUE,即视为无界队列
      • 由于其可选的有界性和基于链表的实现,它通常用于那些生产者和消费者线程数量可变的场景。
    • SynchronousQueue
      • 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程的对应移除操作,反之亦然。
      • 它非常适合于传递性场景,即在其中,一个任务由插入任务的线程启动,但必须等待另一个线程接收(或取走)该任务才能继续。
      • 使用SynchronousQueue的线程池将不会真正地存储任务,而是直接尝试将任务传递给线程执行,这可能导致新任务创建新线程(如果当前线程数小于最大线程数)。
    • PriorityBlockingQueue
      • 一个支持优先级排序的无界阻塞队列。元素必须实现Comparable接口或在构造队列时提供Comparator,以便进行排序。
      • 这对于需要按优先级顺序执行任务的场景非常有用。
    • LinkedBlockingDeque(虽然不常用作ThreadPoolExecutor的工作队列,但值得一提):
      • 一个由链表结构组成的双端阻塞队列。它可以从两端插入和移除元素。
      • 尽管ThreadPoolExecutor通常不需要双端队列的功能,但LinkedBlockingDeque可以作为BlockingQueue的替代实现,用于其他需要双端队列特性的场景。
  • 线程工厂(ThreadFactory):用于创建新线程的工厂,可以自定义线程的创建过程。
    • 默认的线程工厂

      如果你没有为 ThreadPoolExecutor 显式指定一个 ThreadFactory,那么它将使用默认的线程工厂,这个工厂将创建普通的线程,没有特别的配置(比如,线程名称将是自动生成的,通常是“pool-N-thread-M”的形式,其中N是线程池的编号,M是线程的编号)。

    • 自定义线程工厂的形式

      • 设置线程名称:最常见的自定义形式之一是为线程设置有意义的名称,以便于在日志或调试时识别它们。

        • public class NamedThreadFactory implements ThreadFactory {  
              private final String namePrefix;  
              private final AtomicInteger threadNumber = new AtomicInteger(1);  
          
              public NamedThreadFactory(String namePrefix) {  
                  this.namePrefix = namePrefix + "-";  
              }  
          
              @Override  
              public Thread newThread(Runnable r) {  
                  Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());  
                  if (t.isDaemon())  
                      t.setDaemon(false);  
                  if (t.getPriority() != Thread.NORM_PRIORITY)  
                      t.setPriority(Thread.NORM_PRIORITY);  
                  return t;  
              }  
          }
          ThreadPoolExecutor a=new ThreadPoolExecutor(10, 20, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(10), new NamedThreadFactory("ggg"));
                  a.submit(()->{
                      System.out.println(Thread.currentThread().getName() + " is running with value 1");
                  });
                  a.submit(()->{
                      System.out.println(Thread.currentThread().getName() + " is running with value 1");
                  });
                  a.submit(()->{
                      System.out.println(Thread.currentThread().getName() + " is running with value 1");
                  });
                  a.submit(()->{
                      System.out.println(Thread.currentThread().getName() + " is running with value 1");
                  });

      • 设置线程优先级:根据任务的紧急程度,为线程设置不同的优先级。

      • 设置线程为守护线程JVM中只剩下守护线程时,JVM会自动退出。守护线程通常用于执行那些对程序终止不敏感的任务,如日志记录、监控或垃圾回收等。这些任务对于程序的运行不是必需的,但它们的运行可以提高程序的效率或提供额外的功能

        • t.setDaemon(true)

        • t.start()

      • 设置未捕获异常处理器:为线程设置 UncaughtExceptionHandler,以便在线程因未捕获的异常而终止时执行特定的操作。

      • 结合使用:将上述特性结合起来,创建一个功能丰富的 ThreadFactory

  • 拒绝策略(RejectedExecutionHandler):当工作队列已满,且线程池中的线程数达到最大线程数时,对于新提交的任务的拒绝策略。(就是核心线程都在忙着做任务,工作队列也满了,救急线程也在忙着做任务的情况下,对于其他任务的拒绝策略)以下是4种内置策略:
    • AbortPolicy(中止策略)——默认
      • 行为:当任务被拒绝时,会直接抛出RejectedExecutionException异常。
      • 适用场景:适用于那些不希望有任务被丢弃的场景,可以通过异常处理机制来感知到任务被拒绝的情况,并据此进行相应的处理。
    • CallerRunsPolicy(调用者运行策略)
      • 行为:如果线程池没有关闭,那么任务将由调用execute方法的线程(即提交任务的线程)来执行。
      • 适用场景:适用于那些不希望任务被丢弃,且任务的执行对性能影响不大的场景。但需要注意的是,如果提交任务的线程本身就有重要的任务要执行,那么可能会因为执行被拒绝的任务而导致性能下降。
    • DiscardPolicy(丢弃策略)
      • 行为:直接丢弃任务,不抛出任何异常。
      • 适用场景:适用于那些可以容忍任务丢失的场景。但需要注意的是,使用此策略可能会导致数据丢失,且不容易被发现。
    • DiscardOldestPolicy(丢弃最老策略)
      • 行为:丢弃队列中等待时间最长的任务,然后尝试执行新任务。
      • 适用场景:适用于那些可以容忍旧任务被丢弃,但希望尽可能执行新任务的场景。但同样存在数据丢失的风险。
    • 通过实现RejectedExecutionHandler接口来自定义拒绝策略

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值