一、线程和线程池
关于线程大家都不陌生,官方说法线程作为进程执行的最小单位。线程池字面上理解就是一堆线程放在一起。打个比方,线程相当于小工,线程池相当于工程队。以前做任务A,招个小工过来,做任务B,招一个小工过来。。。。,这样的话就会出现一个问题,我招小工需要耗费时间精力,如果任务比较小,一会就做完了,那我费半天劲招个小工的时间都比干活的时间长,那我不是亏大了。因此,为了解决我频繁招工的问题,就需要建立一个工程队,这样就有了线程池的概念。有了工程队,现在工作的方式就变了,任务A来了,我招了小工甲,任务B来了,我招了小工乙,后面又来了任务C,小工甲刚好干完了,我直接就分配给小工甲,这次我就不用招工了。如果所有任务都做完了,我直接就解散工程队,整个操作行云流水,完美。总结一下,
1、线程资源比较珍贵。如小工,我招聘要花时间吧,干活还要付费。
2、线程池的出现解决了频繁招工的问题,减少系统创建线程的开销。
3、线程池提高了工作效率,我可以把招工的时间用来干活。
4、线程池可以更好的管理线程。有了工程队,有了组织,那些小工不是妥妥的听话。
综述所述,线程池非常有用。那线程池怎么用呢,这里就要介绍相关的东东了。
二、如何创建线和使用线程池
废话不多说,直接贴代码。
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(new Runnable(){});
上面提供了线程池创建的常用方式,Executors类作为线程池的工具类为我们提供了许多线程池创建的常用方法,主要有:
newCachedThreadPool | 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 |
newFixedThreadPool | 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 |
newScheduledThreadPool | 创建一个定长线程池,支持定时及周期性任务执行。 |
newSingleThreadExecutor | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 |
可以看到一般通过executor.submit来提交一个线程给线程池进行处理。Executors为一个工具类,在生成新的线程池时实际调用的是
ThreadPoolExecutor类的构造函数,如Executor.newCachedThreadPool()方法时,实际调用的如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}。
那问题来了,ThreadPoolExecutor又是一个什么样的存在,作为线程池管理的核心类,如何如传说中的那样管理线程池,合理的调用线程资源来承接各种处理任务呢,下面我们一一道来。
1、关键的ThreadPoolExecutor类
ThreadPoolExecutor作为线程池创建的核心类具有四种构造方法,现在我拷贝一下网上介绍的一张图做下说明:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
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);
...
}
可以看的出来ThreadPoolExecutor继承至AbstractExecutorService类,而AbstractExecutorService是接口ExecutorService 的实现,而ExecutorService接口继承Executor接口,那整个类的关系图如下:
类图上相关的接口和类我们稍后再介绍,我们继续说这个ThreadPoolExecutor的构造函数,从代码上看构造函数主要包含如下参数:
corePoolSize | 核心池的大小,这里要注意,线程池创建的时候里面是没有线程的,只有当有任务来的时候才创建线程。只有调用了prestartAllCoreThreads()或者prestartCoreThread()方法才会在线程池创建的时候创建线程,因此线程池创建的时候线程数为0。这里经常会有面试官挖坑问这个问题,请注意。 |
maximumPoolSize | 线程池最大线程数,表示线程池最多能创建多少线程。比如核心线程5个,最大线程10个,一般情况下就会有5个线程在排队。如果超过这个数字线程则阻塞。 |
keepAliveTime | 一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。 |
unit | 指定 TimeUnit是一个枚举类型,其包括: |
workQueue | 线程池中的任务队列. 常用的有三种队列,
|
threadFactory | 线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法。 public interface ThreadFactory { Thread newThread(Runnable r); } |
RejectedExecutionHandler |
当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。 一般在workqueue容纳不下多余的线程时,会触发这个handler。 |
三、线程池规则
现在我们了解线程池主要工具类Executors及工具类创建的四种线程池及java实际执行线程池类ThreadPoolExecutor,现在我们来看看实际的规则。对照ThreadPoolExecutor构造函数的参数来看。
1、假设任务队列大小没有限制
(1)如果线程数量<corePoolSize,则直接启动核心线程来执行,不会加入workqueue任务队列。
(2)如果线程数量>corePoolSize且<=maxPoolSize, workqueue为LinkedBlockingDeque时,超过corePoolSize的线程会进入任务队列中等待执行。
(3)如果线程数量>corePoolSize且<=maxPoolSize, workqueue为SynchronousQueue时,线程池会创建新线程进行执行,不会放到任务队列中,这些线程不属于核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
(4)如果线程数量>maxPoolSize时,workqueue为LinkedBlockingDeque时,则会进入任务队列等待。由于任务队列没有大小限制,因此线程池的最大线程参数设置是无效的。当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。
2、假设任务队列大小固定
(1)当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
(2)SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。
综上所属,线程池时什么,线程池的好处,怎么用线程池以及默认的ThreadPoolExecutor类默认的构造函数及参数规则都做了简单的描述。后续文章将梳理一下ThreadPoolExecutor如何启用线程,如果做线程管理等方面的源码分析。