趴一趴线程池那点事

 

 

写在前面

    多线程的软件设计,主要是为了最大程度上利用CPU的使用率最大化生产环境的吞吐量和性能。java

若是对线程管理不当,很是容易形成系统崩溃,线程相对进程而言虽然轻量,无止境的使用更会形成内存泄漏,因此线程使用的 度 须要很好的把控好。数据库

   作过数据库连接工做的朋友们,对数据库链接池确定不陌生,用链接池来维护一些激活的数据库连接,须要的时候从链接池取,不须要的时候交换给链接池而不是真正的销毁。设计模式

  

RoadMap

     对于线程池的学习,主要分文2个篇幅,线程池的使用和线程池的实现。数组

 

     使用篇:缓存

     开箱即用,JDK对线程池的支持。微信

              建立线程池     多线程

              线程池的使用 并发

              线程池的生命周期框架

              Futre模式与Callable接口函数

   实现篇:

   扒一扒ThreadPoolExecutor              

             找个地方听任务请求:任务队列

             超负荷了怎么办: reject handler

             

开箱即用,JDK对线程池的支持

   在JDK5.0 引入 Current包以后 提供了对线程池的支持(Executor 框架). 看一下它家的族谱:

    Executor 和ExecutorService 是接口,AbstractExecutorService 是抽象类,实现了公共方法, 它的子类分别指向了不一样类型的Executor, 今天咱们要讲的是这个 ThreadPoolExecutor,线程池。

   Executors 是一个工厂类(别和Executor 接口搞混了哈, 虽然只差了 一个s),它是用来建立各式各样的Executor,其中包括了线程池, 定时调度的任务池等等。

 

  建立线程池

   用Executors 建立线程池 很是简单,调用对应的方法便可,传递的参数 是 线程数量 或者 线程建立工厂,须要自行实现建立线程的方法。

   

 

 

 

newCachedThreadPool()
newCachedThreadPool(ThreadFactory factory)
缓存线程 的线程池 当任务提交过来,若是没有空闲的线程,则建立新线程,来执行任务。

注意: 若是提交任务的速度大于 完成任务的速度,那么会不断的有新线程建立,直到内存耗尽。 因此在使用这类线程池的时候要加倍注意。
newSingleThreadPool(ThreadFactory factory) 单线程的线程池  
newFixedThreadPool(int nThreads)
newFixedThreadPool(int nThreads, ThreadFactory factory)
固定线程数量的线程池 当n =1 的时候 与SingleThreadPool 做用相似

 

线程池的使用

 

void shutdown() 再也不接受新的任务,
待现有的任务完成后关闭线程
List<Runnable> shutDownNow() 尝试中止 正在执行的线程任务,并关闭线程池
返回的是提交给线程池,还没执行的线程组。
boolean isShutdown() 判断线程池是否关闭
boolean isTerminated() 判断线程池是否终止

Shutdown 和 terminated 的区别下文【线程池的生命周期】提到,这与ExecutorService 的生命周期有关。
boolean awaitTermination(long timeout, TimeUnit unit) 接收人timeout和TimeUnit两个参数,阻塞当前线程一段时间来检测线程池的终止状态。
若是终止返回 true。

如下三种状况会结束这种阻塞。
1. 超过设定的等待时间。
2. 等待的这段时间,线程池中的任务所有完成进入 终止状态。
3. 或者当前线程遇到interrupttion
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

 void execute(Runnable command);
提交任务到当前Executor 的任务队列,等待调度完成。

这几个方法的区别在下文【Future模式与Callable】会提到关与名词Future和新接口Callable
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
提交这个集合里面全部的任务,等集合任务全部任务完成 才算完成,这个timeout 是只整个集合的超时时间,而不是单个
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
 <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
提交这个集合里面全部的任务,等集合任务任意一个任务完成 才算完成,这个timeout 是只整个的超时时间

 

线程池的生命周期

线程池,简单能够分为三个阶段, 运行阶段, 关闭阶段,终止阶段。

  运行阶段没什么好说的, 主要是区分一下关闭阶段和终止阶段。

  打个比方可能会好理解一点,把线程池比作超市,游客比作线程,超市通常是晚上10点中止营业,10点之后就是关闭状态,试下一下,若是10之后你还在超市里面逛,超市并不会把杀死对吗, 只是催着你赶忙去结帐对吗, 另外超市晚上10点之后就不能进游客了。 当游客都走了,账结算完 超市晚上才正式歇业(终止).

   类比回线程池,那么线程池关闭,不能提交任务,会将正在处理(不包含队列中)的任务执行完。 线程所有完成以后 进入终止状态。

 

Future模式与Callable  

 

Callable 是在java.util.concurrent 包中新接口,若是要实现线程, 实现Runnable 和Callable 接口均可以,

区别在于, runnable 没有返回值,不抛出异常,one way的方式,因此使用runnable的时候,异常基本上都是线程内部处理,不可以交给主线程来处理,

甚至有实现,这个线程挂了或有没有执行完都不知道。

Callable 摆脱了前面说的这种状况,它加了范性的返回值,同时容许抛出异常,这样对多线程的调度和处理更加灵活一些。

 

Future是JDK内置的并发设计模式中的Future模式,有兴趣的同窗能够做为扩展阅读深刻了解一下,这个经典模式。

这里简单的介绍一下:

    通常状况,若是要拿到方法的返回值 是否是要等方法运行完,若是这个方法要运行好久,那岂不是要等好久。

那开子线程不是不用等了吗?非也,线程在计算完以前,可能这个结果是个null,对你后面的逻辑也是有影响的。

因此就引入了FutureData的概念,子线程计算的结果 对我来讲是一个FutureData, 我只是须要知道 它计算完毕以后我去取一下数据就能够了。

或者你能够这么认为,这个Future对象是一个中介,它持有对线程计算结果的引用同时也有线程计算完成的状态,你以后问一下中间人,计算完了吗?

完了 那么把结果给我。

 

扒一扒ThreadPoolExecutor

   用Executors建立线程池的时候 经过IDE点进那个方法进去 你会发现,哇塞 都是调用了同一个类的构造方法。That is ThreadPoolExecutor.

 

   ThreadPoolExecutor 的构造方法是被重载的,整体归纳起来,须要设定这么几个参数

   corePoolSize :  指定线程池 常驻线程的数量,

   maximumPoolSize:  指定线程池 最大的线程数量

   keepAliveTime: 容许线程最大空闲时间, 若是当线程数大于corePoolSize的时候 会释放空闲的线程来节约内存。

   TimeUnit: 空闲时间的单位

   BlockingQueue<Runnable>  任务队列, 提交到线程池的任务就是放在这的。

   ThreadFactory  线程建立工厂用来建立线程

   RefectedExecutionHandler :  主要用于 当须要处理 任务队列满了以后 拒绝提交状况下的处理。

 

逻辑图:

 

 

找个地方听任务请求:任务队列

     这里须要具体说说 这个任务队列,构造函数里面的声明的是,BlockingQueue, 这是个接口 根据功能不一样能够运用好几种不一样的队列。

 

  • 直接提交队列:该功能由SynchronousQueue 实现, 是一个特殊的BlockingQueue,由于SynchronousQueue 没有容量,没插入一个操做都须要等待对应的删除操做,反之每个删除操做都须要等待对应的插入,因此提交的任务是不被保存的,每次都提交给线程,若是没有线程则建立新的线程,若是到达线程上线则须要执行拒绝策略,一般来讲要使用这种Queue 则须要设定很大的MaximumPoolSize,不然可能会拒绝掉不少请求。
  • 有界任务队列: 该功能由ArrayBlockingQueue实现, 其内部是数组实现因此 初始化的时候须要定义容量,当线程数小于corePoolSize时,会优先建立线程处理队列的中的任务,若是线程数大于corePoolSize,则存放在队列,存满的时候,若是线程数没有超过maximunPoolSize,则建立线程,不然就须要执行决绝策略。
  • 无界任务队列: 该功能由LinkedBlockingQueue实现, 由于是链表结构 因此能够实现无界的任务队列,当线程数小于corePoolSize时,会优先建立线程处理队列的中的任务,若是线程数等于corePoolSize,不会再建立新的线程,后面的任务会一直添加到任务队列中,由于没有界,没有必要准备拒绝策略来执行,要注意的是,若是添加任务的速度大于处理任务的速度,会出现无限扩容 直到内存耗尽的状况。
  • 优先任务队列:该功能由PriorityBlockingQueue实现,它是一个特殊的无界队列,常规的队列都是先进先出,PriorityBlockingQueue能够处理优先级高的任务。

     

超负荷了怎么办: 决绝策略。

    前文提到过不少次决绝策略, 这究竟是什么鬼呢。 其实也很好理解 就是任务队列满了以后 须要处理下后续请求,说是说拒绝策略,也未必真的就把人家个拒绝了。

   JDK内置了4种策略

  • AbortPolicy: 中断策略, 该策略是抛出异常,中断系统运行
  • CallerRunsPolicy: 不经过线程池的线程来执行任务,容易形成系统资源紧张。有点想
  • DiscardOldestPolicy: 丢弃老请求策略: 若是执行程序还没有关闭,该策略是会把最老的请求放弃 ,即将要处理的请求,就是把队列头的任务放弃掉,新的任务入队列。
  • DiscardPolicy:丢弃请求策略: 该策略是会放弃没法处理的请求,其实就是默默丢弃任务的作法。和AbortPolicy相似只是不抛异常

    

 

回看Executors创建的几个线程池:

  Executors在建线程池的时候,就是在这个几个参数的上作选择,以实现不一样类型的线程池

 

  WorkQueue corePoolSize MaximumPoolSize aliveTime Reject Handler
singleThreadPool LinkedBlockingQueue 1 1 0 second AbortPolicy

FixedThreadPool

LinkedBlockingQueue 定义的线程数 定义的线程数 0 second AbortPolicy
cachedThreadPool SynchronousQueue 0 Integer.MAX_VALUE 60 second AbortPolicy

 

       JDK 默认的Policy 是AbortPolicy ,尽管没有传递这个参数,JDK 默认采用终止策略,

有意思的是,cachedThreadPool 用的是SynchronousQueue,即来了请求就开线程,线程空闲60秒就销毁。

 

欢迎关注微信公众号

相关文章
相关标签/搜索