美文网首页
并发编程的艺术-线程池源码解析

并发编程的艺术-线程池源码解析

作者: Java耕耘者 | 来源:发表于2018-10-21 13:44 被阅读10次

    线程池的作用:

     1,降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

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

     3,提高系统的客观理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

    一,线程池的实现原理

    当想一个线程池提交一个新任务时,线程池的处理流程如下:

     1,线程池判断核心线程池里面的线程是否都在执行任务,如果核心线程未满,则创建一个新的工作线程来执行任务。如果核心线程已满,则进如下一个流程判断。

     2,线程池判断工作队列是否已经满了,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列已经满了,则进入下个流程。

     3,线程池判断线程池的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则提交给饱和策略来处理这个任务。

    ThreadPoolExecutor执行execute方法,有如下四种情况:

     1,如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获取全局锁)。

     2,如果运行的线程等于或者多余corePoolSize,则将任务加入BlockingQueue。

     3,如果无法将任务加入BlockingQueue,则创建新的线程来处理任务(需要获取全局锁)。

     4,如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectExecutionHandler.rejectedExecution方法。

     ThreadPoolExecutor采取上述步骤的总体设计思路,是为了执行execute方法时候,尽可能避免获取全局锁。在ThreadPoolExecutor完成预热之后(即当前运行的线程数目大于等于核心线程数目),几乎所有的execute方法调用的都是执行步骤2,而步骤2是不需要获取全局锁的,保证了性能。

    ThreadPoolExecutor的使用

    线程池的创建

    ​ 我们要用线程池来统一分配和管理我们的线程,那首先我们要创建一个线程池出来,还是有很多大牛已经帮我们写好了很多方面的代码的,Executors的工厂方法就给我们提供了创建多种不同线程池的方法。因为这个类只是一个创建对象的工厂,并没有涉及到很多的具体实现,所以我不会过于详细地去说明。

    ​ 老规矩,还是直接上代码吧。

    publicstaticExecutorServicenewFixedThreadPool(intnThreads){returnnewThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,newLinkedBlockingQueue());  }

    这里也就举出一个方法的例子来进行之后的讲解吧,我们可以看出,Executors只是个工厂而已,方法也只是来实例化不同的对象,实际上实例化出来的关键类就是ThreadPoolExecutor。现在我们就先来简单地对ThreadPoolExecutor构造函数内的每个参数进行解释一下吧。

    corePoolSize(核心线程池大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,当任务数大于核心线程数的时候就不会再创建。在这里要注意一点,线程池刚创建的时候,其中并没有创建任何线程,而是等任务来才去创建线程,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法 ,这样才会预先创建好corePoolSize个线程或者一个线程。

    maximumPoolSize(线程池最大线程数):线程池允许创建的最大线程数,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界队列,此参数就没有意义了。

    keepAliveTime(线程活动保持时间):此参数默认在线程数大于corePoolSize的情况下才会起作用, 当线程的空闲时间达到keepAliveTime的时候就会终止,直至线程数目小于corePoolSize。不过如果调用了allowCoreThreadTimeOut方法,则当线程数目小于corePoolSize的时候也会起作用.

    unit(keelAliveTime的时间单位):keelAliveTime的时间单位,一共有7种,在这里就不列举了。

    workQueue(阻塞队列):阻塞队列,用来存储等待执行的任务,这个参数也是非常重要的,在这里简单介绍一下几个阻塞队列。

    ArrayBlockingQueue:这是一个基于数组结构的有界阻塞队列,此队列按照FIFO的原则对元素进行排序。

    LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按照FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()就是使用了这个队列。

    SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool()就使用了这个队列。

    PriorityBlockingQueue:一个具有优先级的无阻塞队列。

    handler(饱和策略);当线程池和队列都满了,说明线程池已经处于饱和状态了,那么必须采取一种策略来处理还在提交过来的新任务。这个饱和策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。共有四种饱和策略提供,当然我们也可以选择自己实现饱和策略。

    AbortPolicy:直接丢弃并且抛出RejectedExecutionException异常

    CallerRunsPolicy:只用调用者所在线程来运行任务。

    DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

    DiscardPolicy:丢弃任务并且不抛出异常。

    如何一起学习,有没有免费资料?

    在程序员这条路上遇到瓶颈的朋友可以加WX:daxigua012 大家一起来提升进步 但要备注好信息 ,分享知识

    关注下面公众号"Java这点事"获取BATJ等一线互联网企业面试题目和答案还有java技术干货知识等你领取                                                                                                                              

    相关文章

      网友评论

          本文标题:并发编程的艺术-线程池源码解析

          本文链接:https://www.haomeiwen.com/subject/ycdbzftx.html