1.引入线程池的原因
对于多线程编程,处理每个请求都要创建一个线程,这不仅要花费时间在创建线程的过程中,还会出现创建线程过多未释放导致的系统内存不足,内存溢出问题,因此引入线程池的概念。线程池,就是在一个容器中创建适量的线程,在程序访问的时候直接调用该线程即可访问。
2.类比数据库连接池。
数据库连接池与线程池类似,dao层访问数据库时,首先会,加载驱动,建立连接,而每次频繁的建立连接肯定会大大降低系统运行效率,因此,数据库连接池出现了,下面以一张图进行说明:
如上图,没连接池时访问一次数据库便建立一个Connection,用完,连接释放,添加数据库连接池后,是连接先建立好或者使用时建立,用完,不释放,而是放在数据库连接池中,供下一次请求调用。
3.线程池的执行流程
在具体了解线程池之前,有必要先了解一下,线程池的执行流程。
<1>.当提交一个新任务到线程池中时,先判断线程池中的当前线程数是否大于线程池基本大小(corePoolSize),小于corePoolSize:创建一个新任务来执行当前线程。
<2>.大于corePoolSize,将任务存放入阻塞队列中(假定此时的阻塞队列是有界的),阻塞队列未满:存放入阻塞队列,等待线程执行。
<3>队列已满:判断线程池中的线程数量是否大于maximumPoolSize(线程池的最大容量),小于,创建新线程来处理该任务。
<4>线程池中的线程数量大于maximumPoolSize(线程池的最大容量),即线程池中的所有线程都处于工作状态,此时任务会被拒绝,会交给饱和策略来进行处理。
4.了解java.util.concurrent包下的ThreadPoolExecutor类
ThreadPoolExectuor类是线程池的核心类,其可以创建线程池。
<1> 构造方法(创建线程池时使用):
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler);
参数:
corePoolSize - 池中所保存的线程数,包括空闲线程,线程池中的基本线程数。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。即放在阻塞队列中的线程的最大等待时间,
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的阻塞队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序,拒绝执行任务时的策略。
<2>主要方法:
1》public void execute(Runnable command):向线程池提交一个任务 command,交由线程池去执行。若执行失败则会调用RejectedExecutionHandler,拒绝执行任务策略进行处理。
具体源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
execute方法就是按照上面的线程执行流程来的,
《2 submit 和execute()一样,用于向线程池提交任务,
区别:submit提交具有返回值的任务,execute()无返回值。
《3 shutdown,shutdownNow
区别:主要在是否关闭正在执行的线程。
shutdown:逐个遍历线程池中的线程,调用中断处理,中断没有正在执行的线程。
shudownNow:将线程池的状态设置为stop,停止所有正在执行或停止任务的线程。
5.几种类与接口间的继承,实现关系图
类ThreadPoolExecutor继承类AbstractExecutor
类AbstractExecutor实现接口ExecutorService接口
ExecutorService接口继承Executor接口
6.几种具ThreadPoolExecutor
<1>FixedThreadPool,可重用固定线程数的线程池
其创建过程调用 ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
其中:
corePoolSize, maximumPoolSize为一个具体的固定的值。
keepAliveTime:0L,即多余空闲线程等待新任务的时间为0s,线程池中若存在空闲线程,则会立即被终止。
这儿阻塞队列,采用了LinkedBlockingQueue无界阻塞队列(队列的最大容量为Integer.MAX_VALUE)。
采用无界队列带来的影响:
1.线程池中的正在执行的线程数目不会超过corePoolSize
原因:超过corePoolSize,便会放在阻塞队列中等待被执行,阻塞队列永远不会满,不会再创建新的线程来执行任务。
2.maximumPoolSize,线程池中的最大线程数成为无用参数。很好理解。
3.FixedThreadPool,永远不会拒绝任务,不会使用上面说的几种拒绝策略。(永远不会达到线程池的最大线程数目)
<2>SingleThreadPool(单个线程池)
调用ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
其中:
corePoolSize, maximumPoolSize 设置为1,使用单个线程。
keepAliveTime:0L,即多余空闲线程等待新任务的时间为0s,线程池中若存在空闲线程,则会立即被终止。
这儿阻塞队列,采用了LinkedBlockingQueue无界阻塞队列,单个线程无限反复的从阻塞队列中拿出任务进行执行。
同样无界队列带来的问题,SingleThreadPool也有。
<3>.CachedThreadPool(按需分配的线程池)
调用ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
其中:
corePoolSize,默认设置为0,
maximumPoolSize ,设置Integer.MAX_VALUE,即线程池的最大线程数是无界的。
keepAliveTime:60L,即多余空闲线程等待新任务的时间为60s,
这儿阻塞队列,采用了SynchronousQueue无界阻塞队列,SynchronousQueue:一个没有容量的阻塞队列,在插入之前必须有线程从队列中拿走的操作,
CachedThreadPool:按需分配的线程池,就是主线程offer任务1,线程池中查找有无空闲线程,无则创建新线程让其执行任务1,同时,任务2来了,查看有空闲线程,让其执行任务2,还有空闲线程,等待60s若还没有任务执行,则终止。
具体实践:
1.线程池应用之自主实现一个线程池
2.使用Executors中提供的线程池实现生产者-消费者模型
参考自:java并发编程的艺术(方腾飞)