我们为什么要使用线程池,直接new thread start
不好吗?
我们先来看看线程池的一个执行流程图,此图来自文末参考1
通过上述图我们可以得出线程池执行任务可以有以下几种情况:
coreSize
,则创建新线程来执行任务。coreSize
或多余coreSize
(动态修改了coreSize
才会出现这种情况),把任务放到阻塞队列中。在java jdk
的Executors
有提供创建不同线程池的方法(一般不推荐这种做法)阿里巴巴的开发手册也明确强制规定不让通过Executors
来创建的,在一些公司的开发规范里面应该也会有这么一条吧。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
我们可以看出创建线程池有七个参数,而上述我们通过Executors
工具类来创建的线程池就一两个参数,其他参数它都帮我们默认写死了,我们只有真正理解了这几个参数才能更好的去使用线程池。下面我们来看看这七个参数(线程池参数)。
prestartAllCoreThreads
()方法线程池就会为我们提前创建好所有的基本线程。corePoolSize
时,keepAliveTime
才会起作用,直到线程池中的线程数不大于corePoolSize
,即当线程池中的线程数大于corePoolSize
时,如果一个线程空闲的时间达到keepAliveTime
,则会终止,直到线程池中的线程数不超过corePoolSize
。但是如果调用了allowCoreThreadTimeOut
(boolean)方法,在线程池中的线程数不大于corePoolSize
时,keepAliveTime
参数也会起作用,直到线程池中的线程数为0;比如当前线程池中最大线程数(maximumPoolSize)为50,核心线程数(corePoolSize)为10,当前正在跑任务的线程数为30.然后是不是空出了20个线程没活干,所以这20个线程就要被消毁,有点卸磨杀驴的感觉。如果剩下的30个线程干完活了也休息了keepAliveTime这么久,然后这30个线程里面也要被销毁20个,就保留个核心线程。如果设置了allowCoreThreadTimeOut
等于true
核心线程也会被销毁。就跟我们做外包项目一样,甲方项目完成了就得去另外一个甲方,如果短时间内都没有甲方接纳你的话,你就要被辞退了,只会留下几个核心人员维护下项目,如果甲方项目维护的话用自己的人的话,所有的外包人会都会被辞退。days
、hours
等。任务队列。可以选择以下这些队列
用户设置创建线程的工厂,我们可以通过这个工厂来创建有业务意义的线程名字。我们可以对比下自定义的线程工厂和默认的线程工厂创建的名字。
默认产生线程的名字 | 自定义线程工厂产生名字 |
---|---|
pool-5-thread-1 | testPool-1-thread-1 |
阿里开发手册也有明确说到,需要指定有意义的线程名字。
其实我们也可以自定义任务拒绝策略(实现下RejectedExecutionHandler
接口),比如说如果任务拒绝了我们可以记录下日志,或者重试等,根据自己的业务需求来实现。
dubbo
任务拒绝策略 @Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
String msg = String.format("Thread pool is EXHAUSTED!" +
" Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: "
+ "%d)," +
" Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(),
e.getLargestPoolSize(),
e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
url.getProtocol(), url.getIp(), url.getPort());
logger.warn(msg);
dumpJStack();
dispatchThreadPoolExhaustedEvent(msg);
throw new RejectedExecutionException(msg);
}
我们可以看出dubbo
的拒绝策略主要记录了详细的级别为warm的日志、输出当前线程堆栈详情、继续抛出拒绝任务异常。
线程池既然有这么多参数那么我们如何去根据自己的业务实际情况来去合理的设置每个参数?
IO
型比如读取数据库、文件读写以及网略通信的的话这些任务不会占据很多cpu
的资源但是会比较耗时:线程数设置为2倍CPU数以上,充分的来利用CPU
资源。CPU
密集型任务,这种该怎么设置线程大小?这种的话最好分开用线程池处理,IO
密集的用IO
密集型线程池处理,CPU
密集型的用cpu密集型处理。以上都只是理算情况下的估算而已,真正的合理参数还是需要看看实际生产运行的效果来合理的调整的。有了这些参数我们是不是调整线程池的参数就更加方便了。或者根据线程池的活跃程度我们自动来调节(动态调整下篇再来说)线程池的参数。