凡事都在事上磨,这样才能精进,成长进步提高! ------ 博客园首页

详解线程池

一,作用

1.减少资源的开销,没有每次需要的提供系统资源进行创建和销毁线程,提高系统的响应

2.请求到来,线程在池中直接使用,提高响应

3.实现对线程的管控.线程池可以对线程的创建与停止、线程数量等等因素加以控制,使得线程在一种可控的范围内运行,不仅能保证系统稳定运行,而且方便性能调优。

4.总结: 线程复用,线程管控,控制最大并发数(平衡高性能和高可用,稳定性)

 

二,原理

1.角色: 分成多个工作线程和一个阻塞队列,共同协作,调度并行处理任务,提任务的处理量,缩减处理时间.

2.工作线程: 一组已经处于运行中的线程,不断向阻塞队列中领取任务执行

3.阻塞队列: 是用来存储工作线程来不及处理的任务,当工作线程都在执行任务,新来的任务只能暂存在的阻塞队列中.

 

 

三,参数:

1.创建一个线程池: new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, runnableTaskQueue,threadFactory,handler);

核心线程数:corePoolSize --- 空闲时间,基本保持稳定数量的线程数量

最大线程数:maximumPoolSize  ---  最大线程数量,线程数量的上线,如果是实际线程已经达到这个上限,队列未满,任务加入队列,队列已满,执行拒绝策略.

线程空闲存活时间:keepAliveTime --当实际线程数量超过corePoolSize时,若线程空闲的时间超过该值,就会被停止。
PS:当任务很多,且任务执行时间很短的情况下,可以将该值调大,提高线程利用率。

时间单位:timeUnit

阻塞队列:runnableTaskQueue

    1. ArrayBlockingQueue
      它是一个由数组实现的阻塞队列,FIFO。
    2. LinkedBlockingQueue
      它是一个由链表实现的阻塞队列,FIFO。 吞吐量通常要高于ArrayBlockingQueue。
      fixedThreadPool使用的阻塞队列就是它。 它是一个无界队列。
    3. SynchronousQueue
      它是一个没有存储空间的阻塞队列,任务提交给它之后必须要交给一条工作线程处理;如果当前没有空闲的工作线程,则立即创建一条新的工作线程。 cachedThreadPool用的阻塞队列就是它。 它是一个无界队列。
    4. PriorityBlockingQueue
    5. 它是一个优先权阻塞队列。

线程工厂:threadFactory

拒绝策略:handler

    1. 当实际线程数达到maximumPoolSize,并且阻塞队列已满时,就会调用饱和策略。JDK1.5由四种饱和策略:
      1:AbortPolicy 默认。直接抛异常。
      2:CallerRunsPolicy
      只用调用者所在的线程执行任务。
      3:DiscardOldestPolicy
      丢弃任务队列中最久的任务。
      4:DiscardPolicy
      丢弃当前任务。

 

 

ThreadPoolExecutor运行机制
当有请求到来时:

    1. 若当前实际线程数量 少于 corePoolSize,即使有空闲线程,也会创建一个新的工作线程;
    2. 若当前实际线程数量处于corePoolSize和maximumPoolSize之间,并且阻塞队列没满,则任务将被放入阻塞队列中等待执行;
    3. 若当前实际线程数量 小于 maximumPoolSize,但阻塞队列已满,则直接创建新线程处理任务;
    4. 若当前实际线程数量已经达到maximumPoolSize,并且阻塞队列已满,则使用饱和策略。

 

 

 

 

提交任务
可以向ThreadPoolExecutor提交两种任务:Callable和Runnable。

  1. Callable
    该类任务有返回结果,可以抛出异常。
    通过submit函数提交,返回Future对象。
    可通过get获取执行结果。
  2. Runnable
    该类任务只执行,无法获取返回结果,并在执行过程中无法抛异常。
    通过execute提交。

关闭线程池
关闭线程池有两种方式:shutdown和shutdownNow,关闭时,会遍历所有的线程,调用它们的interrupt函数中断线程。但这两种方式对于正在执行的线程处理方式不同。

    1. shutdown()
      仅停止阻塞队列中等待的线程,那些正在执行的线程就会让他们执行结束。
    2. shutdownNow()
      不仅会停止阻塞队列中的线程,而且会停止正在执行的线程。

 设置合理的线程池大小
任务一般可分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。

     CPU密集型任务,尽量使用较小的线程池,一般为CPU核心数+1。

    • 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。
    • IO密集型任务
      可以使用稍大的线程池,一般为2*CPU核心数。
      IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
    • 混合型任务
      可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。
      只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
      因为如果划分之后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

 

Executor两级调度模型
在这里插入图片描述

 

在HotSpot虚拟机中,Java中的线程将会被一一映射为操作系统的线程。
在Java虚拟机层面,用户将多个任务提交给Executor框架,Executor负责分配线程执行它们;
在操作系统层面,操作系统再将这些线程分配给处理器执行。

 

Executor结构在这里插入图片描述

 

Executor框架中的所有类可以分成三类:

 

  1. 任务
    任务有两种类型:Runnable和Callable。
  2. 任务执行器
    Executor框架最核心的接口是Executor,它表示任务的执行器。
    Executor的子接口为ExecutorService。
    ExecutorService有两大实现类:ThreadPoolExecutor和ScheduledThreadPoolExecutor。
  3. 执行结果
    Future接口表示异步的执行结果,它的实现类为FutureTask。

 

线程池
Executors工厂类可以创建四种类型的线程池,通过Executors.newXXX即可创建。

 

1. FixedThreadPool

 

public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

 

  • 1
  • 2
  • 3

 

v2-984ba405f67249ab3cc043c75dbcbedd_hd.jpg

 

  • 它是一种固定大小的线程池;
  • corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
  • keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效;
  • 阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;
  • 由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;
  • 由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。

 

2. CachedThreadPool

 

	public static ExecutorService newCachedThreadPool(){
	    return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
	}

 

  • 1
  • 2
  • 3

 

v2-f9cff0865c6143ace452274046322335_hd.jpg

 

  • 它是一个可以无限扩大的线程池;
  • 它比较适合处理执行时间比较小的任务;
  • corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
  • keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
  • 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。

 

3. SingleThreadExecutor

 

public static ExecutorService newSingleThreadExecutor(){
    return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

 

  • 1
  • 2
  • 3

 

在这里插入图片描述

 

  • 它只会创建一条工作线程处理任务;
  • 采用的阻塞队列为LinkedBlockingQueue;

 

4. ScheduledThreadPool

 

它用来处理延时任务或定时任务。

 

 

  • 它接收SchduledFutureTask类型的任务,有两种提交任务的方式:

  • scheduledAtFixedRate

  • scheduledWithFixedDelay

  • SchduledFutureTask接收的参数:

  • time:任务开始的时间

  • sequenceNumber:任务的序号

  • period:任务执行的时间间隔

  • 它采用DelayQueue存储等待的任务

  • DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序;

  • DelayQueue也是一个无界队列;

  • 工作线程的执行过程:

  • 工作线程会从DelayQueue取已经到期的任务去执行;

  • 执行结束后重新设置任务的到期时间,再次放回DelayQueue

 

  * 线程池三种使用方式
 * 1、Executors.newFixedThreadPool(5) 适合长期任务,性能好
 * 2、Executors.newSingleThreadExecutor() 一个任务一个任务执行,保证顺序性
 * 3、Executors.newCachedThreadPool() 适合短期异步任务或者负载很轻的服务

 

因为相信,所以看见.
posted @ 2021-04-07 00:15  zeenzhou  阅读(29)  评论(0编辑  收藏  举报