一.线程池的使用规范
阿里巴巴开发手册规定,线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让开发人员更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 - CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
二.线程池的7个参数你都知道吗?
构造方法中的参数信息
查看源代码
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。
- maximumPoolSize 线程池最大线程数量
当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列(后面会介绍)中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
- keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
- unit 空闲线程存活时间单位
keepAliveTime 空闲线程存活时间计量单位
- workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
①ArrayBlockingQueue
1.基于数组的有界阻塞队列,按FIFO排序。
2.新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。
3.当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。
4.如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
1.基于链表的无界阻塞队列(最大容量为Interger.MAX),按照FIFO排序。
2.由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数)。
3.使用该工作队列时,参数maxPoolSize其实是不起作用的。
③SynchronousQuene
1.一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。
2.新任务进来时,不会缓存,直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
1.具有优先级的无界阻塞队列。
2.优先级通过参数Comparator实现。
-
threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等操作。
-
handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,我们通过拒绝策略来解决这个问题,jdk中提供了4中拒绝策略:
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
②AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
④DiscardOldestPolicy
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
三.线程池的5种状态
-
RUNNING
- 线程池被创建,线程池的状态就是RUNNING状态;
- 线程池处于RUNNING状态时,线程池能够接收新任务,也能够对已经添加的任务进行处理;
-
SHUTDOWN
- 线程池已经被关闭了,不再接收新任务;但是,其还是会处理队列中的剩余的任务;
- 调用线程池的shutdown()方法后,线程池的状态就会由RUNNING转为SHUTDOWN;
-
STOP
- 线程池处于STOP状态,此时线程池不再接收新任务,不处理已经添加进来的任务,并且会中断正在处理的任务;
- 调用线程池的shutdownNow()方法后,线程池的状态就会由RUNNING或SHUTDOWN转为STOP;
-
TIDYING
- 线程池被下达关闭命令后,如果当前所有的任务都已经终止了(这个终止可以表示执行结束,也可以表示强制中断,也可以表示被丢弃) ,那么线程就会进入TIDYING状态;当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
- 如果线程状态已经是SHUTDOWN了,并且线程中以及队列中都没有任务时,线程池就会由SHUTDOWN转为TIDYING;如果线程池状态为STOP,那么当线程池把所有的任务都给清理干净时,线程池就会由STOP转为TIDYING;
-
TERMINATED;
- 线程池结束,不能重新启动了;
- 如果线程池处于TIDYING状态,那么当线程池执行完terminated()方法后,线程池状态就会由TIDYING转为TERMINTED;
四.线程池的执行流程
-
提交任务后会首先进行当前工作线程数与核心线程数的比较,如果当前工作线程数小于核心线程数,则直接调用 addWorker() 方法创建一个核心线程去执行任务;
-
如果工作线程数大于核心线程数,即线程池核心线程数已满,则新任务会被添加到阻塞队列中等待执行,当然,添加队列之前也会进行队列是否为空的判断;
-
如果线程池里面存活的线程数已经等于核心线程数了,且阻塞队列已经满了,再会去判断当前线程数是否已经达到最大线程数 maximumPoolSize,如果没有达到,则会调用 addWorker() 方法创建一个非核心线程去执行任务;
-
如果当前线程的数量已经达到了最大线程数时,当有新的任务提交过来时,会执行拒绝策略
五.总结
学习不仅输入,还要输出,接下来我们一起来回顾一下这篇文章的内容吧
- 线程池的使用规范
- 线程池的7的参数
- 线程池的4种拒绝策略
- 线程池的5种状态
- 线程池的执行流程
好了,具体的细节没有想起来的可以翻上去看看,这篇文章就学习到这里,接下来我们一起来学习关于多线程相关的知识。
你准备好了吗?来吧
我是硕风和炜,我们下篇文章见哦。