线程池概述
线程池提供了一种限制和管理资源(包括执行一个任务)。 使用线程池的好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executor框架概述
Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好。Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让 并发编程变得更加简单。
- 任务需要实现的 Runnable 接口 或 Callable 接口。
- 任务的执行(Executor) 包括任务执行机制的核心接口 Executor ,以及继承自 Executor 接口的 ExecutorService 接口。 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 这两个关键 类实现了ExecutorService 接口。 我们需要更多关注的是 ThreadPoolExecutor 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的
- Future 接口以及Future接口的实现类 FutureTask 类都可以代表异步计算的结果。当我们把 Runnable 接口 或 Callable 接口 的实现类提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor执行。(调用 submit() 方法时会返回一个 FutureTask对象)
创建线程池
在《阿里巴巴 Java 开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供
,不允许
在应用中自行显示创建线程
。另外《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 工具类去创建
,而是通过 ThreadPoolExecutor 构造函数的方式
,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
ThreadPoolExecutor直接创建
ThreadPoolExecutor 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方 法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么)
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 (重要参数)
int maximumPoolSize,//线程池的最大线程数 (重要参数)
long keepAliveTime,//当线程数大于核心线程数时,多余的空 闲线程存活的最长时间 (常见参数)
TimeUnit unit,//时间单位 (常见参数)
BlockingQueue<Runnable> workQueue,//任务队列,用来储 存等待执行任务的队列 (重要参数)
ThreadFactory threadFactory,//线程工厂,用来创建线程, 一般默认即可 (常见参数)
RejectedExecutionHandler handler) //拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
{
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException(); //判断参数合法性
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException(); //判断参数合法性
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor 3 个最重要的参数:
- corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
- maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变 为最大线程数。
- workQueue : 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,线程就会被存放在队列中。
ThreadPoolExecutor 其他常见参数:
- keepAliveTime :当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,
核心线程外的线程
不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime 才会被回收销毁; - unit : keepAliveTime 参数的时间单位。
- threadFactory :executor 创建新线程的时候会用到。
- handler :饱和策略。关于饱和策略下面单独介绍一下。
ThreadPoolTaskExecutor 定义一些策略:
- ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException 来拒绝新任务的 处理。
- ThreadPoolExecutor.CallerRunsPolicy :调用执行自己的线程运行任务,也就是直接在调用 execute 方法的线程中运行( run )被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此 这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。 如果您的应用程序可以承受此延迟并且你不能丢弃任何一个任务请求的话,你可以选择这个策略。 ThreadPoolExecutor.CallerRunsPolicy 。当最大池被填满时,此策略为我们提供可伸缩队列。
- ThreadPoolExecutor.DiscardPolicy : 不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。
原理分析:例如:我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次 只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务执行完成后,才会执行剩下的 5 个任务。过程如下图所示:
几种常见的方法介绍
Runnable
接口不会返回结果或抛出检查异常,但是 Callable 接口可以。 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;submit()
方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get() 方法来获取返回值, get() 方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit) 方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。shutdown()
:关闭线程池,线程池的状态变为SHUTDOWN
。线程池不再接受新任务了,但是队列里的任务得执行完毕。shutdownNow()
:关闭线程池,线程的状态变为 STOP 。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的List。isShutDown
当调用 shutdown() 方法后返回为 true。isTerminated
当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true
线程池状态
➢RUNNING :能接受新提交的任务, 并且也能处理阻塞队列中的任务
➢SHUTDOWN :不再接受新提交的任务,但可以处理存量任务
➢STOP :不再接受新提交的任务,也不处理存量任务
➢TIDYING :所有的任务都已终止
➢TERMINATED : terminated()方法执行完后进入该状态
线程池中工作线程的生命周期:
利用Executors
创建不同的线程池满足不同场景的需求
通过 Executor 框架的工具类 Executors 来实现,我们可以创建三种
类型的ThreadPoolExecutor:FixedThreadPool 、SingleThreadExecutor、 CachedThreadPool
。对应 Executors 工具类中的方法如下:
newFixedThreadPool(int pThreads)
指定工作线程数量的线程池,可重用固定线程数的线程池。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
2. newSingleThreadExecutor()
创建唯一的工作者线程来执行任务,如果线程异常结束,会有另一个线程取代它
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
//可以看出新创建的 `SingleThreadExecutor` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 1.其他参数和 `FixedThreadPool` 相同。
3. newCachedThreadPool()
处理大量短时间工作任务的线程池,
(1) 试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;
(2) 如果线程闲置的时间超过阈值,则会被终止并移出缓存;
(3) 系统长时间闲置的时候,不会消耗什么资源
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
CachedThreadPool
的corePoolSize
被设置为空(0),maximumPoolSize
被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPool
中线程处理任务的速度时,CachedThreadPool
会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。
-
newSingleThreadScheduledExecutor()与newScheduledThreadPool(int corePoolSize)
定时或者周期性的工作调度,两者的区别在于单一工作线程还是多个线程,ScheduledThreadPoolExecutor
主要用来在给定的延迟后运行任务,或者定期执行任务。(了解即可)
ScheduledThreadPoolExecutor
使用的任务队列DelayQueue
封装了一个PriorityQueue
,PriorityQueue
会对队列中的任务进行排序,执行所需时间短的放在前面先被执行(ScheduledFutureTask
的time
变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(ScheduledFutureTask
的squenceNumber
变量小的先执行)。 -
newWorkStealingPool()
内部会构建ForkJoinPool ,利用working-stealing算法,并行地处理任务,不保证处理顺序
本文是通过学习“慕课网”视频,总结的学习笔记,也仅作为学习笔记,如有侵权请谅解,请勿转载。