什么是线程池?
从字面的意思来理解就是一个池子里放了很多的线程,而java中的线程是一个Thread对象,池就对应于某种数据结构,所以线程池简单来说就是某种数据结构里存放了很多的线程对象。在java中的线程池ThreadPoolExecutor类中的属性works就是实际意义上的线程池,它是一个HashSet类型的变量。
为什么要线程池?
最常见的我们的Web服务器,每次一个客户发送请求就需要创建一个线程去处理这个请求,处理完请求后就需要销毁线程。java中的线程是映射到操作系统的线程的,这样的情况下创建于销毁线程的开销是比较大的,所以就需要线程池了,当一个线程执行完任务后,不会立即销毁而是向任务队列中拿取任务继续执行任务。
java中的线程池的实现
java的线程池的实现是ThreadPoolExecutor类,通过ThreadPoolExecutor类创建的对象调用executor方法,将实现了callable或runnable接口的对象传递给executor方法,就可以让线程池中的线程去执行任务。那ThreadPoolExecutor内部是如何运行的呢?首先看ThreadPoolExecutor的构造方法的源码如下,
可以看出前3个构造方法都是调用了最后一个构造方法,这个构造方法有7个参数,每个参数的含义如下。
—corePoolSize:核心线程数,即正常情况下线程池中的线程数。
—maximumPoolSize:最大线程数,即线程池中所能创建的最大线程数
—keepAliveTime:存活时间,当闲置的线程空闲时间超过这个参数,就销毁这个线程。
—unit:存活时间的单位。
—workQuene:任务队列,当线程池中的线程数到达了核心线程数,就将后续提交的任务放到这个 队列中。
—threadFactory:线程工厂:通过这个类型的对象来创建线程池中的线程。
—handler:拒绝策略,当线程数满了,任务队列满了,用这个策略来处理新到的任务。
提交到线程池的任务如何执行的?
看看ThreadPoolExecutor的execute方法的源码。
从源码中我们可以看出,execute执行的任务必须是实现了Runnable接口的对象。其中最核心的代码就是调用了addWorker方法。看看addWorker方法的源码如下。
从addWorker的源码中可以看出,addWorker方法内部创建了一个Worker对象,那Worker是什么?看看Worker的部分源码如下。
通过Worker源码中的构造方法发现,创建Worker对象的时候会利用线程工厂创建一个线程,并且将实现了Runable接口的worker对象传递给了线程作为参数,所以JVM会调用worker对象的run方法,而run方法内部调用了runWorker方法,并且传递了自身作为参数。看看runWorker方法的源码如下。
从源码可以看出,runWorker才是真正的执行任务的方法,在内部调用了execute传递过来的实现了Runnable接口的对象的run方法,在执行完后,又继续循环,调用getTask方法,只有当getTask方法返回了null的时候这个线程才会停止。getTask方法的源码如下。
从源码中可以看出,该方法内部会调用workQuene队列的poll方法和take方法获取任务队列中的任务,如果任务队列中没有任务,则返回null。poll和take方法的内容大致如下:当线程数超过核心线程数就会调用poll方法,当poll方法阻塞了keepAliveTime指定的时间后就会返回null,getTask方法返回null,线程进行销毁。当线程数不超过核心线程数,执行take方法,一直挂起当前线程。
线程池当中涉及的一些概念理解?
线程池的5种状态:
RUNNING:接受新的任务,并且运行任务队列中的任务。
SHUTDOWN:不接受新的任务,但运行任务队列中的任务。
STOP:不接受新的任务,也不执行队列里的任务,并且终止正在执行的任务。
TIDYING:当所有的任务已终止,ctl记录的任务为0,线程池就是tidying状态,并且会调用钩子方法terminated(),在ThreadPoolExecutor类中的该方法为空,用户若想再这个状态做些别的事情可以对该方法进行重写。
terminated:当在tidying状态下执行完terminated方法后,就会变为这个状态,线程池正真的终止了。
线程池的初始化:
默认情况下创建线程池后,是没有线程的,只有在有任务的时候才会创建线程去执行该任务。如果需要在线程池创建好就立马创建线程可以调用方法.
用于任务队列的三种数据结构:
ArrayBlockingQueue —基于数组的先进先出队列,此队列创建时必须制定大小。
LinkedBlockingQueue —基于链表的先进先出队列,长度默认为integer的最大值。
synchronousQueue —这个队列不会保存提交的任务,而是直接创建线程来执行新的任务。这要求maximumPoolSize不绑定一个特定的值。
任务拒绝策略:当线程池的线程数量达到maximumPoolSize时,任务队列也满了时,如果还有新的任务便会执行拒绝策略。
ThreadPoolExecutor.AbortPolicy —抛弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy —抛弃任务但是不抛出异常。
ThreadPoolExecutor.CallerRunsPolicy —由调用线程处理该任务。
ThreadPoolExecutor.DiscardOldestPolicy —抛弃队列头的任务,然后尝试执行任务,重复这个过程。
线程池的关闭
public void shutdown() —不会立即关闭线程池,而是等待缓存队列的任务执行完毕,期间不会再接受新的任务。
public List<Runnable> shutdownNow() —立即终止线程池,并且尝试中断正在执行的任务,返回未执行的任务列表。
线程池的动态容量调整
public void setCorePoolSize(int corePoolSize)
public void setMaximumPoolSize(int maximumPoolSize)
线程池的任务处理机制:
当线程池在刚创建初期,线程池中的线程数还少于核心线程数,这时接收到的新任务,线程池会创建一个新的线程去执行该任务。当线程数到达核心线程数,这个时候的新任务会被放入我们指定的任务队列,在线程池的线程执行完当前的任务的时候,会一直向任务队列中获取任务,如果获取不到任务,在线程空闲一段时间后就会被销毁。当线程池的线程数达到核心线程数,但是并没有超过最大线程数并且任务队列满了,这时候新接收的任务会被直接创建线程去执行,当线程数达到最大的线程数的时候,这时在接收的新任务会采取相应的剧决策略来处理。