一:前置了解
线程池类图如下:
在阿里巴巴的编程规范中关于线程池有三条原则:
原则一.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。创建线程池的时候请使用带ThreadFactory的构造函数,并且提供自定义ThreadFactory实现或者使用第三方实现。
原则二.线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
原则三.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
二:核心参数
以上三条原则出现的原因时根据线程池实现原理总结出来的,下面我们来详细了解线程池的相关知识:
在ThreadPoolExecutor类中有如下构造方法:
还有其他的三种构造方法,但是最后都会调用此构造方法。该方法中的核心参数为:
1)corePoolSize 核心池的大小 默认为0 调用prestartAllCoreThreads()和prestartCoreThreds()方法时才会预先创建线程
2)maximumPoolSize:线程池最大线程
3)keepAliveTime:线程池中 在最大线程数大于核心线程数时才会起作用 即线程在空闲状态下保存的时间
4)TimeUnit :keepAliveTime的时间单位
5)workQueue:一个阻塞队列 用来保存工作线程 一般有以下三种选择
ArrayBlockingQueue 基于数组的先进先出队列,此队列创建时必须指定大小
LinkedBlockingQueue 基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为
Integer.MAX_VALUE;
SynchronousQueue 这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执
行新来的任务。
ThreadFactory :线程池工厂 用来生成工作线程,通常用来为线程命名,方便检索日志发现问题。
RejectedExecutionHandler :拒绝任务处理策略(根据参数配置有可能不需配置)
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
ThreadPoolExecutor 重要方法:
execute() 像线程池提交任务
submit() 也是向线程池提交任务 调用的时excute()方法 但是有一个Future返回值来获取执行结果
shutdown():关不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
功能实现
当前线程池的线程数小于corePoolSize时,会直接创建新的线程来执行任务
如果当前线程数大于等于corePoolSize而小于maximumPoolSize时,会将尝试将任务放入workQueue中,如果放入成功则等待空闲线程来执行,如果放入失败则直接创建线程来执行
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
线程池创建方法
Executors.newCachedThreadPool();将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程
Executors.newSingleThreadExecutor(); 将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
Executors.newFixedThreadPool(int); 创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
补充
在前置三原则第二条原则中,阿里给出了以下说明:
Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
根据我们上面了解到的知识可以知道:线程池的workQueue和maximumPoolSize参数会在使用过程中产生OOM的问题。
平常使用场景中newCachedThreadPool主要处理能快速返回的线程,这样线程池就不会堆积大量线程造成OOM又能提高程序响应时间,newFixedThreadPool处理那些较慢的线程,比如数据库操作,IO操作。当出现OOM时,再根据线程的执行情况调整这些核心参数来满足业务需要。