现在程序运行环境都是多核cpu的环境,为了充分的压榨cpu,多线程的编程模式是避免不了的。线程是有生命周期的,如果每个任务都创建一个线程,在任务结束后线程的生命周期就终止,那么势必会导致线程频繁的创建和销毁,会带来性能的损失。另外,随意的创建线程会导致线程不可能,比如线程过多导致资源耗尽等。因此有了线程池,而JAVA为了减少开发者的负担,在JDK中提供了线程池的实现。
JAVA线程池的核心实现类:ThreadPoolExecutor。其关键构造函数为:
corePoolSize:核心线程数。如果没有设置allowCoreThreadTimeOut,当线程池中的线程数小于等于核心线程数时,尽管超过空闲时间,线程也不会被回收。
maximumPoolSize:最大线程数。任务队列满了同时线程达到最大限制时,在添加任务就会被线程拒绝。
keepAliveTime:空闲时间。这个时间是当线程数超过核心线程数时,线程的空闲时间,超过过这个时间,线程将被回收。
unit:空闲时间的单位。
workQueue:任务队列。用于存放通过execute提交的任务。
threadFactory:线程工厂类。线程池通过这个工厂类创建线程。通常为了方便定位问题,会在这工厂中为线程设置名字。
handler:当线程池饱和拒绝提交的任务的时候被回调的方法。
线程池的一般生命周期:线程池在被初始化后,内部是没有线程的。当任务被提交的时候,会检查当前线程池中的线程数是否下于核心线程数,如果小于则创建线程执行任务。如果当前线程池中的线程数已经超过了核心线程数,则会尝试将任务添加到任务队列,如果此时队列可以容纳任务,则任务会被添加大任务队列,等待空闲出来的任务来执行。如果此时队列也已经满了,那么就会去尝试再创建一个线程,但是尝试创建线程时,会检查线程数是否小于最大线程数,小于时才可创建。在无法创建新线程时任务就会被拒绝,此时会回调handler,默认的handler是抛出异常。在调用shutdown之后,线程将不再接收新任务,已经提交的任务会被执行完。
上述是一般情况,可以通过prestartCoreThread初始化一个线程和prestartAllCoreThreads初始化全部核心线程。这样在提交任务的时候,线程池中就已经存在线程了,加快了处理。
不过在实际应用中,很少直接构造线程池,而是通过Executors类的工厂方法创建线程池。在理解了上述线程池的参数和生命周期之后可以很容易的理解这些工厂方法。
newFixedThreadPool:固定线程数的线程池。核心线程数等于最大线程数,空闲时间为0,空闲时间的值对此线程无影响,队列为无限队列。正是因为队列是无限队列,所以线程数永远不会超过核心线程数。
newSingleThreadExecutor:固定线程数线程池的特例。单线程线程池。
newCachedThreadPool:缓存线程池。核心线程池为0,最大线程数为Integer.MAX_VALUE,空闲时间为60s,队列为SynchronousQueue,这个队列无大小,消费者放到这里面的元素要被直接被消费。
newWorkStealingPool:java7中新增加的模式fork/join模式的线程池,非ThreadPoolExecutor。在后续写完线程池原理之后,会写写这种模式。
newScheduledThreadPool:周期执行任务的线程池,也不是ThreadPoolExecutor。
在系统中,我们该如何设置线程池的这些参数呢?
一般需要根据任务的类型来配置线程池大小:如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1。如果是IO密集型任务,参考值可以设置为2*NCPU。当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。
关注下方微信公众号:java圈子,获取价值999元全套java入门到进阶的学习资料以及教程,还有java技术交流群一起交流学习哦。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!