线程池提供了一种限制和管理资源(包括执行一个任务)的方式。
-
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
-
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
-
Runnable
和Callable
的区别实现
Runnable
接口的任务不会返回结果或者抛出异常检查;实现Callable
接口的任务则可以。 -
submit()
和execute
方法的区别execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;submit()
方法用于提交需要返回值的任务。线程池会返回一个Future
类型的对象,通过这个Future
对象可以判断任务是否执行成功,并且可以通过Future
的get()
方法来获取返回值,get()
方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完 -
线程池任务的执行—
Executor
接口线程池任务执行的核心接口为
Executor
接口,以及继承自Executor
接口的ExecutorService
****接口。ThreadPoolExecutor
和ScheduledThreadPoolExecutor
这两个关键类实现了ExecutorService
接口。- Executor 框架的使用示意图
- 主线程首先要创建实现
Runnable
或者Callable
接口的任务对象。 - 把创建完成的实现
Runnable
/Callable
接口的 对象直接交给ExecutorService
执行:ExecutorService.execute(...)
或者也可以把Runnable
对象或Callable
对象提交给ExecutorService.submite(...)
执行。 - 如果执行
ExecutorService.submit(...)
,ExecutorService
将返回一个实现Future
接口的对象(FutureTask 对象)。由于 FutureTask
实现了Runnable
,我们也可以创建FutureTask
,然后直接交给ExecutorService
执行。 - 最后,主线程可以执行
FutureTask.get()
方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)
来取消此任务的执行。
- 主线程首先要创建实现
- Executor 框架的使用示意图
-
线程池状态
ThreadPoolExecutor
使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量。 -
ThreadPoolExecutor
类的介绍以及核心参数ThreadPoolExecutor
是Execute
最核心的类,其构造方法如下:public ThreadPoolExecutor(int corePoolSize, // 核心线程数量 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 当线程数大于核心线程数时,多余的空闲线程存活的最长时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列,用来储存等待执行任务的队列 ThreadFactory threadFactory, // 线程工厂,用于创建线程 RejectedExecutionHandler handler // 拒绝策略,当提交线程过多来不及处理时,根据拒绝策略来处理任务 )
-
运行机制
- 线程池中刚开始的时候没有线程,当一个任务提交之后,线程池会创建一个新的线程来执行任务
- 当线程数量达到
corePoolSize
且没有线程空闲的时候,再加入的新任务会进入到workQueue
中排队等待,直到有空闲的线程 - 如果
workQueue
选择的是有界队列,那么当等待的任务数量超过了workQueue
的大小时,会创建maximumPoolSize-corePoolSize
数目的临时线程来执行任务。 - 如果线程到达
maximumPoolSize
仍然有新任务这时会执行拒绝策略。ThreadPoolTaskExecutor
定义一些策略:
ThreadPoolExecutor.AbortPolicy
:抛出RejectedExecutionException
来拒绝新任务的处理。ThreadPoolExecutor.CallerRunsPolicy
:让调用者自己执行本次任务。ThreadPoolExecutor.DiscardPolicy
:不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy
: 此策略将丢弃最早的未处理的任务请求。
- 超过
corePoolSize
的临时线程如果一段时间内没有任务做,则需要结束线程。这个时间由TimeUnit和keepAliveTime
决定。
-
常见的线程池
-
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
特点:
corePoolSize
==maximumPoolSize
(没有救急线程被创建),因此也无需超时时间且阻塞队列是无界的,可以放任意数量的任务;它存在的问题是:运行中的FixedThreadPool
(未执行shutdown()
或shutdownNow()
)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。<aside> 💡 适用于任务量已知,相对耗时的任务
</aside>
-
CachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
特点:
核心线程数量为0,最大线程数为Integer.MAX_VALUE,临时线程的生成时间为60s,意味着全部都是临时线程(60s 后可以回收),且临时线程可以无限创建。在极端情况下,这样会导致耗尽 CPU 和内存资源。
<aside> 💡 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况。
</aside>
-
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
特点:
创建的
SingleThreadExecutor
的corePoolSize
和maximumPoolSize
都被设置为 1,其他参数和FixedThreadPool
相同。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。SingleThreadExecutor
使用无界队列作为线程池的工作队列会对线程池带来的影响与FixedThreadPool
相同。说简单点就是可能会导致 OOM。<aside> 💡 希望多个任务排队执行。
</aside>
-
ScheduledThreadPoolExecutor
-
-
关闭线程池
Shutdown()
:线程池状态便为 shutdown,不会接收新的任务,但已提交的任务会执行完,此方法不会阻塞调用线程的执行。ShutdownNow()
:线程池状态变为 stop,不会接收新的任务,会将队列中等待的任务返回,并用interrupt的方式中断正在执行中的任务。
-