线程池的使用说明
线程池主要由两个概念组成:一个是任务队列;另一个是工作者线程。工作者线程主体就是一个循环,循环从队列中接受任务并执行,任务队列保存待执行的任务。
ThreadPoolExecutor
继承自AbstractExecutorService,实现了ExecutorService。
ThreadPoolExecutor实现了生产者/消费者模式,工作者线程就是消费者,任务提交者就是生产者,线程池自己维护任务队列。当我们碰到类似生产者/消费者问题时,应该优先考虑直接使用线程池,而非“重新发明轮子”,应自己管理和维护消费者线程及任务队列。
两个构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
第二个构造方法多了两个参数threadFactory和handler,这两个参数一般不需要,第一个构造方法会设置默认值。
参数说明
入参 | 说明 |
corePoolSize | 核心线程个数 表示线程池中的核心线程个数,不是一开始就创建这么多线程,刚创建一个线程池后,实际上并不会创建任何线程。 有新任务到来的时候,如果当前线程个数小于corePoolSiz,就会创建一个新线程来执行该任务,需要说明的是,即使其他线程现在也是空闲的,也会创建新线程。 线程个数小于等于corePoolSize时,默认情况下:
不过,ThreadPoolExecutor有如下方法,可以改变这个默认行为:
|
maximumPoolSize | 最大线程个数 表示线程池中的最多线程数,线程的个数会动态变化,但这是最大值,不管有多少任务,都不会创建比这个值大的线程个数。 如果线程个数大于等于corePoolSize,那就不会立即创建新线程了,它会先尝试排队,需要强调的是,它是“尝试”排队,而不是“阻塞等待”入队,如果队列满了或其他原因不能立即入队,它就不会排队,而是检查线程个数是否达到了maximumPoolSize,如果没有,就会继续创建线程,直到线程数达到maximumPoolSize。 |
keepAliveTime unit | 空闲线程存活时间 keepAliveTime的目的是为了释放多余的线程资源,它表示,当线程池中的线程个数大于corePoolSize时额外空闲线程的存活时间。也就是说,一个非核心线程,在空闲等待新任务时,会有一个最长等待时间,即keepAliveTime,如果到了时间还是没有新任务,就会被终止。如果该值为0,则表示所有线程都不会超时终止。 |
workQueue | 任务队列 要求的队列类型是阻塞队列BlockingQueue。 注意:
|
threadFactory | 线程工厂 类型为接口ThreadFactory:
这个接口根据Runnable创建一个Thread, ThreadPoolExecutor的默认实现是Executors类中的静态内部类DefaultThreadFactory,主要就是创建一个线程,给线程设置一个名称,设置daemon属性为false,设置线程优先级为标准默认优先级,线程名称的格式为:pool-<线程池编号>-thread-<线程编号>。 如果需要自定义一些线程的属性,比如名称,可以实现自定义的ThreadFactory。 |
handler | 任务拒绝策略 如果队列有界,且maximumPoolSize有限,则当队列排满,线程个数也达到了maximumPoolSize,这时,新任务来了,会触发线程池的任务拒绝策略。 ThreadPoolExecutor实现了4种处理方式:
这些拒绝策略都实现RejectedExecutionHandler接口:
当线程池不能接受任务时,调用其拒绝策略的rejectedExecution方法。 注意:
所以,在任务量非常大的场景中,让拒绝策略有机会执行是保证系统稳定运行很重要的方面。 |
查看关于线程和任务数的一些动态数字:
获取状态函数 | 说明 |
public int getPoolSize() | 返回当前线程个数 |
public int getLargestPoolSize() | 返回线程池曾经达到过的最大线程个数 |
public long getCompletedTaskCount() | 返回线程池自创建以来所有已完成的任务数 |
public long getTaskCount() | 返回所有任务数,包括所有已完成的加上所有排队待执行的 |
工厂类Executors
类Executors提供了一些静态工厂方法,可以方便地创建一些预配置的线程池,主要方法有:
public static ExecutorService newSingleThreadExecutor() public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorService newCachedThreadPool()
方法 | 说明 | 使用场景 |
newSingleThreadExecutor | 只使用一个线程,使用无界队列LinkedBlockingQueue,线程创建后不会超时终止,该线程顺序执行所有任务。 | 需要确保所有任务被顺序执行的场合。 |
newFixedThreadPool | 使用固定数目的n个线程,使用无界队列LinkedBlockingQueue,线程创建后不会超时终止。和newSingleThreadExecutor一样,由于是无界队列,如果排队任务过多,可能会消耗过多的内存。 | 在系统负载很高的情况下,newFixedThreadPool可以通过队列对新任务排队,保证有足够的资源处理实际的任务。 |
newCachedThreadPool | 它的corePoolSize为0, maximumPoolSize为Integer.MAⅩ_VALUE, keepAliveTime是60秒,队列为SynchronousQueue。 它的含义是:当新任务到来时,如果正好有空闲线程在等待任务,则其中一个空闲线程接受该任务,否则就总是创建一个新线程,创建的总线程个数不受限制,对任一空闲线程,如果60秒内没有新任务,就终止。 | 如果系统负载不太高,单个任务的执行时间也比较短,newCachedThreadPool的效率可能更高,因为任务可以不经排队,直接交给某一个空闲线程。 |
注意:在系统负载可能极高的情况下,上述方式都不是好的选择,newFixedThreadPool的问题是队列过长,而newCachedThreadPool的问题是线程过多,这时,应根据具体情况自定义ThreadPoolExecutor,传递合适的参数。
#java原理##并发编程#知其然知其所以然,只有掌握了底层原理,借助第一性原理,才可以在日常开发和项目中运用自如,潇洒走江湖。 专为27届毕业生准备,托起您的就业梦。 该专辑会不定时更新,建议27届同学订阅,入职后扎实的基本功可以帮您争取更好的机会和项目。