浅析线程池

线程池的定义和优点

线程池,从字面含义来看,是指管理一组同构工作线程的资源池。线程池是与工作队列密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
“在线程池中执行任务比为每个线程分配一个任务”优势更多。通过重用现有的线程而不是创建线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。通过适当的调整线程池的大小,可以创建足够的线程以便使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败。
在这里插入图片描述
在这里插入图片描述
通过执行器服务来构建线程池,由上图可以看见其实底层实现的线程池是ThreadPoolExecutor,然后基于这个实现又出现了4种常见的线程池。

JDK1.8源码中ThreadPoolExecutor的四个构造器,可以通过构造方法自定义线程池.

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

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

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

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

一些线程池的参数配置意思:

corePoolSize - 核心线程数量,核心线程在闲置的时候也不会被回收,除非设置了Timeout
maximumPoolSize - 线程池大小 = 核心线程数量 + 非核心线程数量
keepAliveTime - 非核心线程的存活时间
unit - 时间单位
workQueue - 工作阻列
handler - 拒绝服务器,有4种拒绝策略

工作队列

工作队列本质上是一个阻塞式队列.核心线程使用完后,会把后续任务放到队列里.常见工作队列有以下几种.
1.ArrayBlockingQueue:数组式阻塞队列,有界,一旦初始化大小无法修改,FIFO
2.LinkedBlockingQueue:链表阻塞队列,可以指定容量也可以不指定,一旦指定无法修改,FIFO
3.SynchronousQueue:同步队列,只能存储一个元素
4.PriorityBlockingQueue:具有优先级的阻塞式队列.当元素逐个取出时,会对元素进行自然排序,默认是升序,要求存入的元素对应类必须实现Comparable接口,重写compareTo方法.如果不指定大小,默认容量为11,迭代遍历的时候不保证排序
5.DelayedWorkQueue:延迟的工作队列,无界

handler解决策略有四种:

在有界队列被填满后,拒绝策略开始发挥作用, 可以通过调用setRejectedExecutionHandler来修改.默认使用AbotPolicy
第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满

第二种DiscardPolicy:新提交的任务无法保存到队列中等待时,也就是无法执行的任务,会被抛弃

第三种DiscardOldSetPolicy: 当新提交的任务无法保存到队列中等待执行时,,抛弃最旧的任务—队列头部的任务,然后重新尝试提交新的任务(如果工作队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将“抛弃最旧的”策略和优先级队列放在一起使用)
第四种CallerRunsPolicy:直接调用execute来执行当前任务. 该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者(调用线程池执行任务的主线程),从而降低新任务的流程。它不会在线程池的某个线程中执行新提交的任务,而是在一个调用了execute的线程中执行该任务。当线程池的所有线程都被占用,并且工作队列被填满后,下一个任务会在调用execute时在主线程中执行(调用线程池执行任务的主线程)。由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得工作者线程有时间来处理完正在执行的任务。在这期间,主线程不会调用accept,因此到达的请求将被保存在TCP层的队列中。如果持续过载,那么TCP层将最终发现它的请求队列被填满,因此同样会开始抛弃请求。当服务器过载后,这种过载情况会逐渐向外蔓延开来——从线程池到工作队列到应用程序再到TCP层,最终达到客户端,导致服务器在高负载下实现一种平缓的性能降低。

线程工厂

每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的. Executors提供的线程工厂有两种DefaultThreadFactory和PrivilegedThreadFactory,默认是DefaultThreadFactory.用户也可以自定义线程工厂。

线程池的工作流程

  1. 默认情况下,创建完线程池后并不会立即创建线程, 而是等到有任务提交时才会创建线程来进行处理。(除非调用prestartCoreThread或prestartAllCoreThreads方法) 。
  2. 当线程数小于核心线程数时,每提交一个任务就创建一个线程来执行,即使当前有线程处于空闲状态,直到当前线程数达到核心线程数。
  3. 当前线程数达到核心线程数时,如果这个时候还提交任务,这些任务会被放到队列里,等到线程处理完了手头的任务后,会来队列中取任务处理。
  4. 当前线程数达到核心线程数并且队列也满了,如果这个时候还提交任务,则会继续创建线程来处理,直到线程数达到最大线程数。
  5. 当前线程数达到最大线程数并且队列也满了,如果这个时候还提交任务,则会触发饱和策略。
  6. 如果某个线程的控线时间超过了keepAliveTime,那么将被标记为可回收的,并且当前线程池的当前大小超过了核心线程数时,这个线程将被终止。

四种常见的线程池:(返回值都是ExecutorService)

  1. Executors.newCachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,工作队列是一个同步队列。适用于耗时少,任务量大的短任务场景。

  2. Executors.newSecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

  3. Executors.newSingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

  4. Executors.newFixedThreadPool:需要指定核心线程的数量,全部都是核心线程,没有非核心线程,工作队列是阻塞式链式队列,可以存储无限个认为.不适合短任务场景,会导致短任务的耗费时间偏长。

参考

https://blog.csdn.net/v123411739/article/details/79124193

猜你喜欢

转载自blog.csdn.net/qq_40531768/article/details/89307456
今日推荐