线程池相关笔记,线程池的实现?四种线程池?重要参数及原理?任务拒接策略有哪几种?

一.Executors: 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。

ThreadPoolExecutor:线程池的具体实现类,一般用的各种线程池都是基于这个类实现的。

  • 设置核心池的数量为 CPU 数的两倍,一般是 4、8,好点的 16 个线程
  • 最大线程数设置为 64
  • 空闲线程的存活时间设置为 1 秒

 

二.corePoolSize:线程池的核心线程数,线程池中运行的线程数也永远不会超过

corePoolSize 个,默认情况下可以一直存活。可以通过设置allowCoreThreadTimeOut为True,此时 核心线程数就是0,此时keepAliveTime控制所有线程的超时时间。

maximumPoolSize:线程池允许的最大线程数;

keepAliveTime: 指的是空闲线程结束的超时时间;

unit :是一个枚举,表示 keepAliveTime 的单位;

workQueue:表示存放任务的BlockingQueue

BlockingQueue:阻塞队列(BlockingQueue)是java.util.concurrent下的主要用来控制线程同步的工具。如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒。同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。具体的实现类有LinkedBlockingQueue,ArrayBlockingQueued等。一般其内部的都是通过Lock和Condition(显示锁(Lock)及Condition的学习与使用)来实现阻塞和唤醒。

threadFactory:每个线程创建的地方 ,可以给线程起个好听的名字,设置个优先级啥的

handler:饱和策略,大家都很忙,咋办呢,有四种策略 

CallerRunsPolicy:只要线程池没关闭,就直接用调用者所在线程来运行任务

AbortPolicy:直接抛出 RejectedExecutionException 异常

DiscardPolicy:悄悄把任务放生,不做了

DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用 execute() 试试看能行不

我们也可以实现自己的 RejectedExecutionHandler 接口自定义策略,比如如记录日志什么的

三.1.newFixedThreadPool

不招外包,有固定数量核心成员的正常互联网团队。

可以看到,FixedThreadPool 的核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。

此外 keepAliveTime  0,也就是多余的空余线程会被立即终止(由于这里没有多余线程,这个参数也没什么意义了)。

而这里选用的阻塞队列是 LinkedBlockingQueue,使用的是默认容量 Integer.MAX_VALUE,相当于没有上限。

因此这个线程池执行任务的流程如下:

  1. 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
  2. 线程数等于核心线程数后,将任务加入阻塞队列 
  3.  
    1. 由于队列容量非常大,可以一直加加加
  4. 执行完任务的线程反复去队列中取任务执行

FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。

2.newSingleThreadExecutor

不招外包,只有一个核心成员的创业团队。

从参数可以看出来,SingleThreadExecutor 相当于特殊的 FixedThreadPool,它的执行流程如下:

  1. 线程池中没有线程时,新建一个线程执行任务
  2. 有一个线程以后,将任务加入阻塞队列,不停加加加
  3. 唯一的这一个线程不停地去队列里取任务执行

听起来很可怜的样子 - -。

SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。

3.newCachedThreadPool

全部外包,没活最多待 60 秒的外包团队。

可以看到,CachedThreadPool 没有核心线程,非核心线程数无上限,也就是全部使用外包,但是每个外包空闲的时间只有 60 秒,超过后就会被回收。

CachedThreadPool 使用的队列是 SynchronousQueue,这个队列的作用就是传递任务,并不会保存。

因此当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。

它的执行流程如下:

  1. 没有核心线程,直接向 SynchronousQueue 中提交任务
  2. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
  3. 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就拜拜

由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。

CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

4.newScheduledThreadPool

定期维护的 2B 业务团队,核心与外包成员都有。

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor, 最多线程数为 Integer.MAX_VALUE ,使用 DelayedWorkQueue 作为任务队列。

ScheduledThreadPoolExecutor 添加任务和执行任务的机制与ThreadPoolExecutor 有所不同。

ScheduledThreadPoolExecutor 添加任务提供了另外两个方法:

  • scheduleAtFixedRate() :按某种速率周期执行
  • scheduleWithFixedDelay():在某个延迟后执行

四。保存待执行任务的阻塞队列

当线程池中的核心线程数已满时,任务就要保存到队列中了。

线程池中使用的队列是 BlockingQueue 接口,常用的实现有如下几种:

  • ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序
  • LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素 
    • 吞吐量通常要高于 ArrayBlockingQueue
    • Executors.newFixedThreadPool() 使用了这个队列
  • SynchronousQueue:不存储元素的阻塞队列 
    • 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
    • 吞吐量通常要高于 LinkedBlockingQueue
    • Executors.newCachedThreadPool使用了这个队列
  • PriorityBlockingQueue:具有优先级的、无限阻塞队列                                  
  •  

  • 五.任务拒绝策略

      当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

    1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

    2.ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

    3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

    4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务  

  • 六.线程池的关闭

      ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
  •  

你可能感兴趣的:(线程池相关笔记,线程池的实现?四种线程池?重要参数及原理?任务拒接策略有哪几种?)