前言
Java线程的创建与销毁需要一定的开销,因此为每一个任务创建一个新线程来执行,线程的创建与开销将浪费大量计算资源。而且,如果不对创建线程的数量做限制,可能会导致系统负荷太高而崩溃。Java的线程既是工作单元,也是执行机制。JDK1.5之后,工作单元与执行机制分离,工作单元包括Runnable和Callable,执行机制由Executor框架执行。
1.Executor框架简介
Executor框架的两级调度模型
在HotSpot线程模型中,Java线程被一对一的映射为本地操作系统线程。(Java线程启动时,会创建一个本地操作系统线程,当Java线程终止时,对应的操作系统线程也会回收)而操作系统会调度所有的线程分配给可用的CPU。
Java多线程程序通常把应用分解成若干个任务。然后使用Executor框架将这些任务映射为固定数量的线程。
这种二级调度模型如下图所示:
Executor框架的结构与成员
Executor的框架结构由三大部分构成:
- 1.任务。
被执行任务需要实现的接口:Runnable接口或Callable接口。
- 2.任务的执行
包括任务执行机制的接口核心接口Executor以及继承自Executor的ExecutorService接口。
- 3.异步计算的结果
包括接口Future和实现FutureTask接口的FutureTask类。
Executor框架的主要成员:
- ThreadPoolExcutor
线程池的核心实现类,用来执行被提交的任务。使用工厂类Executor来创建。Executor可以创建三种类型的ThreadPoolExcutor。如下:
1.FixedThreadPoolExcutor:重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads。适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
2.SingleThreadPoolExcutor:它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。。适用于需要保证顺序地执行各个任务;并且在任意时间点不会有多个线程的应用场景。
3.CachedThreadPoolExcutor:它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列。适用于执行很多短期异步任务的小程序或者是负载较轻的服务器。
- ScheduledThreadPoolExcutor
可以在给定的延迟后运行命令,或者定期执行命令。他可以创建两种类型的ScheduledThreadPoolExcutor。
ScheduledThreadPool和SingleThreadScheduledExecutor,可以进行定时或周期性的工作调度,区别在区别于单一工作线程还是多个工作线程。
- Future接口
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当把Runnable接口或Callable接口的实现类提交给ThreadPoolExecutor时,ThreadPoolExecutor会向我们返回一个FutureTask对象。
- Runnable接口和Callable接口
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。他们的区别是Runnable不会返回结果,而Callable可以返回结果。除了可以自己创建实习Callable接口的对象外,还可以使用工厂类Executors来把一个Runnable包装成一个Callable。
各成员工作方式如下:
2.ThreadPoolExecutor详解
关于ThreadPoolExecutor的工作原理参考之前的博客:线程池工作原理
Executor框架最核心的类是ThreadPoolExecutor。它是线程池的实现类,先看一下它的源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
调用该方法时要传入的参数有以下几个:
- corePoolSize:核心线程池的大小。
- maximumPoolSize:最大线程池的大小。
- unit:keepAliveTime对应的时间单位。
- keepAliveTime:线程池的工作线程空闲后,保存存活的时间。默认对核心线程池的线程不生效。
- workQueue:用来暂时保存任务的工作队列。
方法内部自动调用的参数
- RejectedExecutionHandle (饱和策略)当ThreadPoolExecutor已经关闭或饱和时(达到最大线程池大小且工作队列已满),executor方法将要调用的Handler。
- ThreadFactory 用于创建线程的工厂。
补充
饱和策略有以下几种:
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy: 使用调用者所在线程来运行任务。
- DiscardOldestPolicy: 丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy: 不处理,丢弃掉。
存放线程的任务队列有以下几种:
- ArrayBlockingQueue:基于数组结构的有界阻塞队列,遵循FIFO。
- LinkedBlockingQueue:基于链表结构的无界阻塞队列,遵循FIFO。吞吐量比ArrayBlockingQueue高。
- SynchronousQueue: 不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量比LinkedBlockingQueue高。
- PriorityBlockingQueue:一个具有优先级的无阻塞队列。
下面详细介绍几种ThreadPoolExecutor
1.FixedThreadPool
可重用固定线程数的线程池。它的源码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
通过它的传参可以看到以下信息:
1.corePoolSize和maximumPoolSize都被设置成指定大小nThread。
2.keepAliveTime被设置成了0。
3.使用的无界队列LinkedBlockingQueue。
结合线程池的工作原理可以知道FixedThreadPool的工作流程如下:
1.如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
2.当前运行的线程数大于corePoolSize后(完成预热),将任务加入无界队列中。
3.线程执行完当前的任务后,会循环反复的从LinkedBlockingQueue中获取任务来执行。
由于使用了无界队列,当线程池中的任务达到corePoolSize后,新任务可以一直加入到队列中,不存在队列满的情况,因此也不会执行的后续的操作(队列满了后判断当前线程数是否大于maximumPoolSize,以及执行饱和策略),所以maximumPoolSize,keepAliveTime以及defaultHandler都是无效参数。
2.SingleThreadPool
它的源码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
通过构造参数可以得到如下信息:
1.corePoolSize和maximumPoolSize都被设置成指定为1。
2.其他同FixedThreadPool。
结合线程池的工作原理可以知道SingleThreadPool的工作流程如下:
1.如果线程池当前无运行的线程,则创建一个新线程。
2.在线程池预热后(当前线程池有一个运行的线程),将任务将入LinkedBlockingQueue。
3.线程执行任务后,会循环反复的从队列中获取新任务来执行。
从上面分析可知,SingleThreadPool能满足任务按照顺序执行的场景。
3.CachedThreadPoolExecutor
CachedThreadPoolExecutor是一个会根据需要创建新线程的线程池。它的源码如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通过构造参数可以得到如下信息:
1.corePoolSize设置为0,,maximumPoolSize都被设置成Integer.MAX_VALUE。
2.keepAliveTime被设置成了60s。
3.使用了没有容量的SynchronousQueue作为线程池的工作队列。
这种线程池的工作流程如下:
1.首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行第二步。
2.如果当前maximumPool中为空或没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。
3.在步骤2中新建线程将任务执行完成后,会执行poll,这个poll操作会让空闲线程最多在SynchronousQueue等待60秒,如果60秒内主线程提交了一个新任务(执行步骤1),那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。因此长时间保持空闲的CachedThreadPool不会使用任何cpu资源。
过程有点小复杂,画个图表示一下:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。