在多线程编程的实际开发中,我们势必会需要启动多个线程来处理多个任务。因为线程的创建和销毁需要一定的性能开销,如果每次执行一个任务都创建一个新线程来执行,那么将消耗大量的计算资源。
所以我们需要一个线程池的概念,让使用过的线程可以回收再使用,避免重复创建带来的性能消耗。下面说一下juc包中为我们提供的线程池实现:
Executor框架
juc包中用ThreadPoolExecutor表示一个线程池,任何实现Runnable接口对象都可以被ThreadPoolExecutor调度。而我们通过Executors线程池工厂,可以取得一个特定功能的线程池。juc包中关于线程池的类关系图如下:
Executor
一个接口,其定义了一个接收Runnable对象的方法executor;
ExecutorService
一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法
AbstractExecutorService
ExecutorService执行方法的默认实现
ThreadPoolExecutor
线程池的核心实现类,用来执行被提交的任务;
ScheduledExecutorService
一个可定时调度任务的接口
ScheduledThreadPoolExecutor
ScheduledExecutorService的实现,一个可定时调度任务的线程池,可以指定延迟执行或周期执行;
ThreadFactory
用来实现创建线程的工厂接口,只有一个方法Thread newThread(Runnable r);
Runnable、Callable
两个接口,其实现类可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行;
ForkJoinPool
用于拆分子任务执行的线程池;上文已经介绍过
Future、FutureTask
用于异步计算;上文已经介绍过
那么这个Executor框架如何使用呢?
Executor框架的使用
首先看一张示意图:
我们的任务线程,必定是实现Runnable或Callable接口二者之一的,区别在于Runnable不会返回结果,而Callable可以返回结果。我们还可以通过Executors工厂类将一个Runnable包装成一个Callable:
public static Callable<Object> callable(Runnable task)
public static <T> Callable<T> callable(Runnable task, T result)
这两个方法区别在于,第一个方法用Future对象调用get()方法时将获得null;而第二个方法将得到result。
说回Runnable和Callable。我们可以将一个Runnable对象通过execute()接口提交给ThreadPoolExecutor或ScheduledThreadPoolExecutor执行,或将一个Callable对象通过submit()接口提交给ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。如前文所说,submit()接口会返回一个Future对象,调用其get(0方法可以获得到这个Callable对象的执行结果。
那么这个ThreadPoolExecutor或ScheduledThreadPoolExecutor线程池应该如何创建呢,这就要说说Executors工厂类为我们提供的创建线程池的API了。
创建线程池
我们已经知道,Executors工厂类可以其创建ThreadPoolExecutor或ScheduledThreadPoolExecutor两种线程池。先说说ThreadPoolExecutor的创建接口:
newFixedThreadPool(int nThreads)
返回一个固定线程数量nThreads的线程池。一个新任务提交时,若线程池内线程数未达到nThreads,则创建新线程执行;如果线程数已达到nThreads且有空闲线程时,使用空闲线程执行;如果没有空闲线程,则任务被暂存在一个任务队列(LinkedBlockingQueue,无界队列)中,等有线程空闲时再执行。线程池中的线程执行完毕后立即终止;
newSingleThreadExecutor
返回一个只有一个线程的线程池。一个新任务提交时,若线程池内线程数为0,则创建新线程执行;如果线程池内线程空闲,则使用空闲线程执行;如果没有空闲线程,则任务被暂存在一个任务队列(LinkedBlockingQueue,无界队列)中,等有线程空闲时再执行。线程池中的线程执行完毕后立即终止;
newCachedThreadPool
返回一个根据实际情况调整线程数量的线程池,初始化时线程池内没有线程。每提交一个新任务时,如果线程池为空或没有空闲线程,则创建一个新的线程处理。线程池的最大容量为0x7fffffff(int最大值,32位最大带符号整数),空闲线程如果60s内没有任务分配将被终止。
Executors工厂类还可以创建ScheduledThreadPoolExecutor线程池。ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,可指定延迟执行或周期执行。功能和Timer类型,但是强大在ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数,而Timer只能是单个后台线程。
ScheduledThreadPoolExecutor有如下2个创建ScheduledThreadPoolExecutor的接口:
newSingleThreadScheduledExecutor
返回一个ScheduledExecutorService对象,线程池大小为1;
newScheduledThreadPool(int corePoolSize)
返回一个ScheduledExecutorService对象,可以执行线程数量;
ThreadPoolExecutor和ScheduledThreadPoolExecutor的实现
ThreadPoolExecutor的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
其中,
corePoolSize是核心线程池大小;
maximumPoolSize是最大线程池大小;
workQueue是用来战术保存任务的工作队列;
keepAliveTime是为多余的空闲线程等待新任务的最长时间;
unit是指定keepAliveTime参数的单位;
handle是线程池对拒绝任务的处理策略;
在ThreadPoolExecutor的三个实现中,FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列,而CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,不过SynchronousQueue的maximumPool是无界的。所以,当提交任务的速度大于线程处理任务的速度时,CachedThreadPool将不断地创建新线程。
ScheduledThreadPoolExecutor的构造方法为:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
其只传了一个核心线程池大小corePoolSize,其余的参数默认设定为不限制最大线程池大小,线程执行完任务后直接终止,并且使用DelayQueue来作为线程池的工作队列。
DelayQueue也是一个无界序列,其中保存的是待调度的任务ScheduledFutureTask。ScheduledFutureTask主要包含三个成员变量,包括:
time:long型,表示这个任务将被执行的具体时间;
sequenceNumber:long型,表示添加到ScheduledThreadPoolExecutor的序号;
period,long型,表示任务执行的间隔周期;
DelayQueue中还封装了一个PriorityQueue队列,将ScheduledFutureTask按time从小到达排列。每次ScheduledThreadPoolExecutor去获取或添加任务时,会先获取锁,然后进行任务操作,然后再释放锁。
任务拒绝策略
ThreadPoolExecutor.execute(Runnable command)提供了提交任务的入口,此方法会自动判断如果池子满了的话,则会调用拒绝策略来执行此任务,接口为RejectedExecutionHandler,内置的4中策略分别为AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy。
AbortPolicy
为java线程池默认的阻塞策略,不执行此任务,而且直接抛出java.util.concurrent.RejectedExecutionException异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出;
DiscardPolicy
直接抛弃,任务不执行,空方法;
DiscardOldestPolicy
从队列里面抛弃head的一个任务,并再次execute此task;
CallerRunsPolicy
在调用execute的线程里面执行此command;如果执行程序已关闭,则会丢弃该任务。会阻塞入口;