java线程池
使用线程池的好处:
- 降低资源消耗。重复创建创建和销毁造成浪费
- 提高响应速度。相应的,利用已经创建好的线程,提高响应速度
- 提高线程的可管理性。使用线程池方便统一的分配,调优和监控。
相应的想下,不用线程池,或者使用不当可能带来的问题。
- 如果你已经拥有足够多的线程使所有的CPU保持忙碌状态,那么创建更多的线程反而会降低性能
- 操作系统的线程数的限制,JVM初始化参数,都是会对线程的创建起到约束的,无限的创建,很可能对导致OOM。
比如:web服务,每个请求,使用单独的线程串行执行,效率不行,很多时间浪费在创建销毁线程上。
改进下,如果每来一个请求,就使用创建一个单独的线程去处理的话,避免了上述情况。但是当用户很多或者恶意攻击的话。该负载就会导致问题。
实际工程上一般使用线程池方案,管理线程,优化上述问题。
比如常见的web容器tomcat,在处理请求的地方使用线程池:
全局搜下ThreadPoolExecutor就可以看出来。
在StandardThreadExecutor类里。
protected void startInternal() throws LifecycleException {
taskqueue = new TaskQueue(maxQueueSize);
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
executor.setThreadRenewalDelay(threadRenewalDelay);
if (prestartminSpareThreads) {
executor.prestartAllCoreThreads();
}
taskqueue.setParent(executor);
setState(LifecycleState.STARTING);
}
可以看到使用的ThreadPoolExecutor类自己构造线程池,其中
队列是TaskQueue类(封装的LinkedBlockingQueue队列)。
ThreadFactory自己封装了下,添加了写个性化信息。
线程是大小corePoolSize25 maximumPoolSize200
maxIdleTime 1分钟
线程池的使用:
Executors线程池工具类,提供了以下四个工厂类方法:
- newFixedThreadPool
- newCachedThreadPool
- newSingleThreadExecutor
- newScheduledThreadPool
ExecutorService线程池抽象接口。
ThreadPoolExecutor实现类,继承ExecutorService接口,上述方法124返回结果的实现类。
ScheduledExecutorService继承ExecutorService接口,是3的实现类。
主要看下ThreadPoolExecutor的构造函数的参数,也就是涉及到线程池参数调优的部分。
ThreadPoolExecutor的参数信息:
corePoolSize 核心线程大小
maximumPoolSize 最大线程数大小
keepAliveTime 线程活动保持时间
unit keepAliveTime的单位
workQueue 任务队列
threadFactory 创建线程的工厂
handler 拒绝策略
处理流程:

- 当任务到来的时候,线程池的corePoolSize未满,使用corePoolSize的线程处理。
- 当corePoolSize满了后,任务队列未满的话,把任务添加到队列里。
- 队列满的话,如果maximumPoolSize>corePoolSize的话,会创建新的线程,处理请求。
- 如果maximumPoolSize满了,队列是有界队列的话,再添加任务,就会根据拒绝策略去决定怎么处理了。
- 队列里的任务等着线程池里线程空闲,就会去处理。当队列里的任务处理完,线程就会空闲下来,判断空闲时间超过keepAliveTime,线程就会回收,知道线程数等于corePoolSize。
线程池参数调优及试用场景:
看任务是IO密集型还是计算密集型。
- 如果是计算密集型,可以把线程数设置成N(cpu核数)+1。这样能够充分利用计算机cpu的处理能力。
- 如果是IO密集型的,比如网络IO,磁盘的数据库IO,这种情况可以考虑把线程池的线程数调大些,比如2N(cpu核数)+1,目的也是充分利用了计算机的处理能力。
- 如果程序里两种类型都有,可以考虑两个线程池。会比一个线程是 串行执行效果好。
上边的分析理想情况,都是基于程序单独部署的情况。如果是混部的话,实际效果不会那么好,因为不同的程序都会抢占cpu的时钟周期。
对于队
列的选择,如果执行的任务有优先级的选择,可是使用priorityQueue,一般会考虑使用FIFO队列LinkedBlockingQueue
如果任务有先后依赖关系考虑设置把coreSize=maxSize。
根据业考虑使用有界队列还是无界队列。
有界队列会丢数据(比如线程池任务数据库IO满查询),会使用拒绝策略抛出异常或者打印log。但是这不会影响程序其他的功能。
而无界队列,不会丢,有问题时队列长度会一直增加,如果没有处理的话。会导致OOM,整个JVM就挂了。
所以一般考虑使用有界队列。
ArrayBlockingQueue和LinkedBlockingQueue的区别
LinkedBlockingQueue:先进先出,默认是无界队列。有有界的构造方法。
阻塞队列,在put和take方法使用的是不同的锁。
ConcurrentLinkedQueue非阻塞队列,是用CAS操作保证线程安全。
ArrayBlockingQueue:先进先出,默认有界队列。阻塞队列,put和take使用的是一个锁。
ThreadFactory:
守护线程与普通线程
JVM启动的时候,处理主线程,其他的线程都是守护线程。守护线程负责一些JVM辅助的工作,比如gc。主线程创建的线程都是普通线程。
当一个线程退出时,JVM会检查其他的所有线程,当都是守护线程的时候,JVM就正常退出。有任何一个普通线程还活着,JVM就不会退出。
拒绝策略的选择:
ThreadPoolExecutor类里边提供了四种拒绝策略,不满足可以自己继承RejectedExecutionHandler接口自定义。
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
监控管理:
通过线程池提供的参数进行监控。ThreadPoolExecutor里的一些状态变量。
taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不+ getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。
参考:
http://www.infoq.com/cn/articles/java-threadPool/
java并发编程实战
tomcat,jdk1.8