因为线程是稀缺的资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,所以Java中提供线程池对线程进行统一的分配,调优和监控,你可以把线程池看作是一个存放线程的缓存。
拿普通的web容器来说,当一个请求过来,会为它分配一个线程来执行,想象一下,如果并发的请求数量非常多,但是每个线程执行的时间很短,短到还没有创建和销毁这个线程所用的时间长,甚至还不如一个线程处理完一个请求不销毁再去处理下个请求,那么可不可以让线程执行完任务后不销毁,而是让它继续去执行下个任务?线程池可以的。
一、线程池的使用场景
1、单个任务处理时间比较短
2、需要处理的任务数量很大
二、线程池的优势
1、重用存在的线程,减少线程创建和消亡的开销,提高性能;
2、提高响应速度,任务到达时可以不用等到线程创建就立即执行;
3、提高线程的可管理性;
三、JDK中的线程池

Executor接口是线程池框架中最顶层接口,上图是它的实现关系图谱,从图中可以看出Excutor下有一个重要的子接口叫ExecutorService,可以看到里面定义个线程池的一些行为:
package java.util.concurrent;import java.util.List;import java.util.Collection;public interface ExecutorService extends Executor {//执行Runnable类型的任务,此方法在父接口Executor中,放到这是为了便于理解void execute(Runnable command);//在完成已提交的任务后关闭,不再接受新的任务void shutdown();//立即停止所有已提交的任务,不管有没有完成List<Runnable> shutdownNow();//测试线程池是否已经关闭boolean isShutdown();//测试所有的任务是否已经执行完毕了boolean isTerminated();//提交Callable或者Runnable类型的任务,并且返回一个Future对象Future<?> submit(Runnable task);}
下面看下ThreadPoolExecutor,因为平时开发的时候要么是直接用它,要么继承它自己封装一个线程池,先看下它的构造方法:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
这里说下它几个重要的参数:
corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时候没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
unit:keepAliveTime的单位;
workQueue:用来保存等待被执行的任务的阻塞队列,且任务必须实现Runnable,在JDK中提供了如下阻塞队列:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue;
3、SynchronousQueue:一个不存元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常高于LinkedBlockingQueue;
4、priorityBlockingQueue:具有优先级的无界阻塞队列;
threadFactoty:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory()来创建线程,使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称;
handler:线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了四种策略:
1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;
上面这四种策略都是ThreadPoolExecutor的内部类,当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
四、线程池的运行原理

1、如果当前工作的线程数小于corePoolSize,则创建并启动一个线程来执行新提交的任务;
2、如果当前工作的线程数大于等于corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
3、如果当前工作的线程数大于等于corePoolSize并且小于maximumPoolSize,并且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
4、如果当前工作的线程数大于等于maximumPoolSize,并且线程池的阻塞队列已满,则根据拒绝策略来处理该任务,默认的处理方式是直接抛出异常。
这篇文章就介绍下线程池的原理,源码层面并未深入分析,以后会开篇文章专门分析,源码就有点多了。
PS:回家的滋味太爽了,这才是过年嘛,不过也不能一直玩,学习不能丢,哈哈!




