线程池原理解析

参考博客

一、什么时候使用线程池

  单个任务处理时间比较短,且需要处理的任务数量很大。

二、线程池优势

  1.重用存在的线程,减少线程创建、消亡的开销,提高性能。

  2.提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行。

  3.提高线程的可管理型,可以统一分配、调优和监控。

三、线程池运行机制

1. 核心参数

1 int corePoolSize,           //核心池的大小,核心线程一直会存在,即使没有任务
2 int maximumPoolSize,             //池中允许的最大线程数,这个参数表示了线程池中最多能创建的线程数量
3 long keepAliveTime,         //当线程数大于corePoolSize时,终止前多余的空闲线程等待新任务的最长时间
4 TimeUnit unit,            //keepAliveTime时间单位
5 BlockingQueue<Runnable> workQueue, //存储还没来得及执行的任务 阻塞队列 有界 | 无界
6 ThreadFactory threadFactory,    //执行程序创建新线程时使用的工厂
7 RejectedExecutionHandler handler   //由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序

2. 运行机制----优先使用核心线程,其次阻塞队列,最后非核心线程(临时工)

1 1 判断任务是否为空
2 2 池中线程数小于corePoolSize,新任务都不排队而是直接添加新线程
3 3 池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程
4 4 池中线程数大于等于corePoolSize,workQueue已满,但是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务
5 5 池中线程数大于大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒
6 6 绝,使用handler处理被拒绝的任务

3. 阻塞队列

  阻塞队列:无论并发有多高,永远只有一个线程能够进行队列的入队和出队操作。线程安全。分为有界和无界。队列满,只能进行出队操作,入队阻塞。队列空,只能进行入队操作,出队阻塞。

1 ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2 LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
3 synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
4 PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
有界队列

1.初始的poolSize < corePoolSize,提交的runnable任务,会直接做为new一个Thread的参数,立马执行 。

2.当提交的任务数超过了corePoolSize,会将当前的runable提交到一个block queue中。

3.有界队列满了之后,如果poolSize < maximumPoolsize时,会尝试new 一个Thread的进行救急处理,立马执行对应的runnable任务。

4.如果3中也无法处理了,就会走到第四步执行reject操作。

无界队列
与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新的任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加,若后续仍有新的任务加入,而没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。


4. 拒绝策略

1 ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
2 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
3 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4 ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

5. 主要方法

1 execute() 以向线程池提交一个任务,交由线程池去执行。
2 submit() 这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
3 shutdown() 和 shutdownNow() 是用来关闭线程池的
创建线程池之后,线程池中是没有线程的,需要提交任务后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
4 prestartCoreThread():初始化一个核心线程;默认情况下,
5 prestartAllCoreThreads():初始化所有核心线程

 excute()&submit():https://www.cnblogs.com/by-my-blog/p/10779333.html

6. 线程池五种状态

 

1 1.Running(111)能接受新任务,能处理已添加的任务。
2 2.Shutdown(000 在Running状态执行shutdown())不接受新任务,能处理已添加的任务。
3 3.Stop (001,在Running状态执行shutdownNow()) 不接受新任务,不处理已添加的任务,并且中断当前正在处理的任务。
4 4.Tidying (010,Shutdown状态:阻塞队列为空,线程池中工作线程数量为0   /   Stop状态:线程池中的工作线程数量为0)
5     所有的任务已经终止,ctl记录的‘任务数量’为0,ctl负责记录线程池的运行状态和活动线程数量。
6 5.Terminated (011,Tidying状态执行terminated())线程池彻底终止,则线程转变为Terminated状态。

 7. 线程池种类

1 newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2 newFixedThreadPool:创建一个可重用固定线程数的线程池
3 newSingleThreadExecutor:创建一个使用单个 worker 线程的 Executor
4 newScheduleThreadPool: 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
5 newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
时间轮 VS 优先队列

 8. 核心线程数设置

  一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU的个数)
  如果是CPU密集型应用,则线程池大小设置为N+1
  如果是IO密集型应用,则线程池大小设置为2N+1
  如果一台服务器上只部署这一个应用并且只有这一个线程池,那么这种估算或许合理,具体还需自行测试验证。
  但是,IO优化中,这样的估算公式可能更适合:
    最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
  因为很显然,线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
  下面举个例子:比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

原文地址:https://www.cnblogs.com/qmillet/p/12465789.html