平凡也就两个字: 懒和惰;
成功也就两个字: 苦和勤;
优秀也就两个字: 你和我。
跟着我从0学习JAVA、spring全家桶和linux运维等知识,带你从懵懂少年走向人生巅峰,迎娶白富美!
每一篇文章都是心得总结,跟我学习你就是大牛!
ThreadPoolExecutor源码的学习(基于JDK 1.7)
ThreadPoolExecutor就是我们经常说的大名鼎鼎的线程池,Executors工厂创建的线程池都是该类的实例,通过调节参数的大小创建适用于各个场景的线程池。通过源码我们了解到ThreadPoolExecutor继承了AbstractExecutorService,该抽象类为线程池提供了默认实现。
ThreadPoolExecutor有很多重载的构造函数,所有构造函数最终都调用了一个构造函数,只是有些构造函数有默认参数而已,看下最终调用的构造函数:
上面这个构造函数有很多参数,我们分别来解释每一个参数的意义:
(1)corePoolSize:核心线程数
核心线程池的大小。当总的创建的线程个数小于CorePoolSize个时,都会创建一个新线程来执行提交的任务请求,核心线程池中的线程创建之后不会销毁(任务执行完之后不会销毁,而是处于空闲状态)。默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true
(2)maximumPoolSize:线程池允许最大的线程数
线程池所能容纳的最大线程数。表示设置线程池的大小,指定线程池允许的最大线程数量。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
(3)keepAliveTime:闲置线程最长存活时间
非核心线程中闲置线程最长可以存活的时间,超过这个时间就会被回收。unit:时间单位。
1)如果线程池中的线程个数大于corePoolSize核心线程池大小,且多余的线程(非核心线程)处于空闲状态的时间超过了keepAliveTime设置时间,那么多余的线程将会被终止。当线程池没被使用的时候,这种机制可减少资源消耗。
2)keepAliveTime线程存活时间参数可以被动态改变,通过setKeepAliveTime(long time, TimeUnit unit)方法来动态设置keepAliveTime值。将time设置成Long.MAX_VALUE,且unit设置成NANOSECONDS(纳秒),则表示在线程池关闭(shut down)之前超出核心线程池的且处于空闲状态的线程永远不会终止,永远处于空闲状态!(即:线程永远存活)。
默认情况下(allowCoreThreadTimeOut=false), keep-alive策略只有当线程池中的线程数超过了核心线程池大小(corePoolSize)的时候才会起作用,且只对超出核心线程池大小的线程数起作用,对核心线程池不起作用(核心线程池中的线程永远不会终止,除非线程池shut down关闭)。但是只要keepAliveTime值不为0,就可以通过
allowCoreThreadTimeOut(boolean)方法来将这个超时策略应用到核心线程。(也就是说如何将超时策略用到了核心线程池上之后,当核心线程池中的线程处于空闲状态,且空闲时间超过keepAliveTime设定时间后会被终止掉!)。
(4)unit:指定keepAliveTime的单位
指定keepAliveTime的单位,如:TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
(5)workQueue:线程池中的任务队列
当池中线程数大于corePoolSize时,新来的任务保存到该队列。常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
(6)threadFactory:线程工厂
线程池中的所有线程都是通过这个工厂创建的。用ThreadFactory来创建线程。如果没指定用哪个ThreadFactory,则会使用defaultThreadFactory默认线程工厂来创建线程。defaultThreadFactory默认线程工厂创建的线程具有相同的优先级(NORM_PRIORITY),非守护进程(non-daemon)状态,并且在相同的线程组(ThreadGroup)里。
通过指定自定义ThreadFactory线程工厂(实现ThreadFactory接口的自定义类),在线程工厂里可以设置创建的每个线程的名称,线程所在组,优先级和守护进程状态等等。【By supplying a different ThreadFactory, you can alter the thread's name, thread group, priority, daemon status,etc】
(7)handler:任务拒绝执行的策略
当线程池无法处理新来任务时的处理策略。当Executor执行器shut down关闭 或 工作线程超过maximumPoolSize最大线程数 或 工作队列超过了设置的最大capacity能力的情况下,execute(Runnable)方法提交的任务将会被拒绝(不会执行)!当上述3种情况的任意一种情况发生时,会回调RejectedExecutionHandler接口的rejectedExecution(Runnable, ThreadPoolExecutor)方法,可以实现RejectedExecutionHandler接口的rejectedExecution(Runnable, ThreadPoolExecutor)方法,在其中做一些抛出异常或日志记录的操作!
(1)当一个任务通过submit或者execute方法提交到线程池的时候,如果当前池中线程数(包括闲置线程)小于coolPoolSize,即使其他工作线程是空闲的,也会创建一个新线程来处理该任务;
(2)如果当前池中线程数大于等于coolPoolSize个,则将该任务加入到等待队列。只要有核心线程空闲,那么就会从队列中去获取任务进行处理(重用空闲的核心线程);
(3)如果任务不能入队列,说明等待队列已满,若当前池中线程数小于maximumPoolSize,则创建一个临时线程(非核心线程)执行该任务。
(4)如果当前池中线程数已经等于maximumPoolSize,此时无法执行该任务,根据拒绝执行策略处理,后面还会详细讲解具体的拒绝执行策略。
以上4步是线程池处理到达任务的主要流程。当池中线程数大于coolPoolSize,超过keepAlive时间的闲置线程会被回收掉。注意,回收的是非核心线程,核心线程一般是不会回收的。如果设置allowCoreThreadTimeOut(true),则核心线程在闲置keepAlive时间后也会被回收。任务队列是一个阻塞队列,核心线程执行完任务后会去队列取任务来执行,如果队列为空,线程就会阻塞,直到取到任务。
package com.test.alltest.java.thread.threadPool;import java.util.concurrent.*;import java.util.concurrent.atomic.AtomicInteger;//线程池测试public class Main {
private static AtomicInteger count = new AtomicInteger(0);//线程计数 private static int corePoolSize = 2;//核心线程数 private static int maximumPoolSize = 4;//最大线程数。可以计算,非核心线程数 = maximumPoolSize - corePoolSize = 2 private static long keepAliveTime = 10;//非核心空闲线程最长存活时间 private static TimeUnit timeUnit = TimeUnit.SECONDS;//非核心空闲线程最长存活时间的单位 //指定阻塞队列的类型和队列长度。可以计算,被核心线程数同时处理的任务个数 = corePoolSize + 队列容量 = 2 + 2 = 4 private static BlockingQueue workQueue = new ArrayBlockingQueue(2); //定义线程工厂 private static ThreadFactory myThreadFactory = new ThreadFactory() {
@Override public Thread newThread(Runnable runnable) {
String threadName = "线程" + count.addAndGet(1); Thread t = new Thread(runnable, threadName); return t; } }; //定义任务拒绝执行的策略 private static RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
@Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录异常 // 报警处理等 int iActiveCount = executor.getActiveCount(); int iRemainQueue = executor.getQueue().remainingCapacity(); int iexsitQueue = executor.getQueue().size(); System.out.println("Warn:当前运行的线程数:"+String.valueOf(iActiveCount) + ",当前队列剩余数:" + iRemainQueue + ",当前队列排队的任务数:" + iexsitQueue); } }; public static void main(String[] args) {
//1.初始化线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, workQueue, myThreadFactory, rejectedExecutionHandler ); /** * 2.使用线程池 * 创建7个任务: 根据线程池的初始化参数配置可推测: * (1)2个任务立即执行; * (2)2个任务加入到阻塞队列中等待核心线程执行; * (3)2个任务被2个非核心线程执行; * (4)剩下的1个任务没有线程来执行(被拒绝执行),因此调用【拒绝执行策略】来处理。 */ for(int i=0; i<7; i++){
//执行任务 executor.execute(new Runnable() {
@Override public void run() {
System.out.println("当前执行任务的线程名:" + Thread.currentThread().getName()); long start = System.currentTimeMillis(); while (true){
long end = System.currentTimeMillis(); if((end - start) > 10000){
//10秒 break; } } } }); } }}
Tips:
1)将CorePoolSize和maximumPoolSize值设置成一样,即:创建一个固定大小的线程池.
2)将maximumPoolSize设置成最大值(Integer.MAX_VALUE),表示设置线程池可以容纳任意数量的并发任务(即:任意数量的线程)。
3)大多数情况下,核心(CorePoolSize)和最大线程(maximumPoolSize)池的大小在构造线程池对象的时候进行设置(构造方法中设置);但是也可以使用{setCorePoolSize}和{setMaximumPoolSize}方法动态地更改它们。