线程池的意义
避免线程过多创建产生的资源浪费,及性能瓶颈,控制资源总量,复用线程,加快响应速度,合理利用cpu和内存,便于管理,以及代码的维护更加整洁。
适合场景
服务器端开发,tomcat就用到了线程池。
超过5个线程协作的方法,需要使用线程池。
注意事项
队列积压过多任务,导致内存溢出
线程数量太多导致内存溢出
线程池的参数
线程池的状态
running:接受新任务并处理排队任务
shutdown:不接受任务但是处理排队任务
stop:不接受新任务,也不处理排队任务,并中断正在进行的任务
tidying:所有任务都已终止,workerCount为0,并将运行terminate()钩子方法
terminate:terminate()方法运行完成
线程池核心参数
corePoolSize:核心线程数,当没有满足核心线程数时创建线程执行任务
maximumPoolSize:最大线程数,当任务队列满了,且线程数不满足最大线程数时创建线程执行任务
keepAliveTime:无任务时,线程存活时间,当达到该时间且没有任务时,该线程会被标记成可回收。
unit:时间单位
workQueue:任务队列,存放任务的
threadFactory:线程工厂,通过怎样的工厂类创建线程
handler:当不能工作时的拒绝策略,当达到最大线程数及队列满了的时候,触发拒绝机制,以及线程池关闭的时候
线程数
有关线程池中,线程数的设置;
计算密集型,corePoolSize = cpu 个数 + 1 = maximumPoolSize
IO密集型,则因为线程并不总是在工作,基于线程工作时间与等待时间求比+1 * cpu 的占用比例* cpu 的数量,随着等待时间与工作时间的比例,在一定区间内进行浮动。
如果是混合型的计算需求,那么就具体按照机器的性能,进行设置线程数。
源码中线程数量与线程池的状态用了一个AtomicInteger 进行存储,线程池的状态有5种,使用高3位进行表示,最大线程数其实不能超过61位能标识的最大数。
核心线程与非核心线程之区别
代码分析
开始创建时有些许差异,我们来看java.util.concurrent.ThreadPoolExecutor 的execute 方法:
publicvoidexecute(Runnablecommand) { if (command==null) thrownewNullPointerException(); intc=ctl.get(); if (workerCountOf(c) <corePoolSize) { // 创建核心线程if (addWorker(command, true)) return; c=ctl.get(); } if (isRunning(c) &&workQueue.offer(command)) { intrecheck=ctl.get(); if (!isRunning(recheck) &&remove(command)) reject(command); elseif (workerCountOf(recheck) ==0) // 线程数扩容addWorker(null, false); } elseif (!addWorker(command, false)) reject(command); }
其实可以看到线程池初始化线程时addWorker 方法是传了firstTask 的,扩容线程时则传的是null,这可以是一个差异点,不过当线程池都创建好后,不再有差异,只会存活核心线程数量那么多的线程,达到超时时间的线程会被设置成可销毁状态,有代码为证:
finalvoidrunWorker(Workerw) { Threadwt=Thread.currentThread(); Runnabletask=w.firstTask; w.firstTask=null; w.unlock(); // allow interruptsbooleancompletedAbruptly=true; try { while (task!=null|| (task=getTask()) !=null) { w.lock(); // If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted. This// requires a recheck in second case to deal with// shutdownNow race while clearing interruptif ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); try { task.run(); afterExecute(task, null); } catch (Throwableex) { afterExecute(task, ex); throwex; } } finally { task=null; w.completedTasks++; w.unlock(); } } completedAbruptly=false; } finally { processWorkerExit(w, completedAbruptly); } }
我们可以从上面代码看到的是,当worker 在执行任务的时候,firstTask 被设置成了null,其实跟之后扩容的线程也就没太大区别了;
总结
总结下为什么扩容的线程不包含first task,其实是想让新建的线程在任务队列中获取任务进行执行;
那为什么有的线程初次创建时却携带first task 是因为线程池在创建时并没有创建线程,而是等任务来了以后才创建的线程,不得不佩服JDK 的创造者们啊。
总结的有些仓促,有机会重新整理下内容,大家加油!