多线程的软件设计,主要是为了最大程度上利用CPU的使用率最大化生产环境的吞吐量和性能。java
若是对线程管理不当,很是容易形成系统崩溃,线程相对进程而言虽然轻量,无止境的使用更会形成内存泄漏,因此线程使用的 度 须要很好的把控好。数据库
作过数据库连接工做的朋友们,对数据库链接池确定不陌生,用链接池来维护一些激活的数据库连接,须要的时候从链接池取,不须要的时候交换给链接池而不是真正的销毁。设计模式
对于线程池的学习,主要分文2个篇幅,线程池的使用和线程池的实现。数组
使用篇:缓存
开箱即用,JDK对线程池的支持。微信
建立线程池 多线程
线程池的使用 并发
线程池的生命周期框架
Futre模式与Callable接口函数
实现篇:
扒一扒ThreadPoolExecutor
找个地方听任务请求:任务队列
超负荷了怎么办: reject handler
在JDK5.0 引入 Current包以后 提供了对线程池的支持(Executor 框架). 看一下它家的族谱:
Executor 和ExecutorService 是接口,AbstractExecutorService 是抽象类,实现了公共方法, 它的子类分别指向了不一样类型的Executor, 今天咱们要讲的是这个 ThreadPoolExecutor,线程池。
Executors 是一个工厂类(别和Executor 接口搞混了哈, 虽然只差了 一个s),它是用来建立各式各样的Executor,其中包括了线程池, 定时调度的任务池等等。
用Executors 建立线程池 很是简单,调用对应的方法便可,传递的参数 是 线程数量 或者 线程建立工厂,须要自行实现建立线程的方法。
newCachedThreadPool() newCachedThreadPool(ThreadFactory factory) |
缓存线程 的线程池 | 当任务提交过来,若是没有空闲的线程,则建立新线程,来执行任务。 注意: 若是提交任务的速度大于 完成任务的速度,那么会不断的有新线程建立,直到内存耗尽。 因此在使用这类线程池的时候要加倍注意。 |
newSingleThreadPool(ThreadFactory factory) | 单线程的线程池 | |
newFixedThreadPool(int nThreads) newFixedThreadPool(int nThreads, ThreadFactory factory) |
固定线程数量的线程池 | 当n =1 的时候 与SingleThreadPool 做用相似 |
void shutdown() | 再也不接受新的任务, 待现有的任务完成后关闭线程 |
List<Runnable> shutDownNow() | 尝试中止 正在执行的线程任务,并关闭线程池 返回的是提交给线程池,还没执行的线程组。 |
boolean isShutdown() | 判断线程池是否关闭 |
boolean isTerminated() | 判断线程池是否终止 Shutdown 和 terminated 的区别下文【线程池的生命周期】提到,这与ExecutorService 的生命周期有关。 |
boolean awaitTermination(long timeout, TimeUnit unit) | 接收人timeout和TimeUnit两个参数,阻塞当前线程一段时间来检测线程池的终止状态。 若是终止返回 true。 如下三种状况会结束这种阻塞。 1. 超过设定的等待时间。 2. 等待的这段时间,线程池中的任务所有完成进入 终止状态。 3. 或者当前线程遇到interrupttion |
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); void execute(Runnable command); |
提交任务到当前Executor 的任务队列,等待调度完成。 这几个方法的区别在下文【Future模式与Callable】会提到关与名词Future和新接口Callable |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) |
提交这个集合里面全部的任务,等集合任务全部任务完成 才算完成,这个timeout 是只整个集合的超时时间,而不是单个 |
<T> T invokeAny(Collection<? extends Callable<T>> tasks) <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) |
提交这个集合里面全部的任务,等集合任务任意一个任务完成 才算完成,这个timeout 是只整个的超时时间 |
线程池,简单能够分为三个阶段, 运行阶段, 关闭阶段,终止阶段。
运行阶段没什么好说的, 主要是区分一下关闭阶段和终止阶段。
打个比方可能会好理解一点,把线程池比作超市,游客比作线程,超市通常是晚上10点中止营业,10点之后就是关闭状态,试下一下,若是10之后你还在超市里面逛,超市并不会把杀死对吗, 只是催着你赶忙去结帐对吗, 另外超市晚上10点之后就不能进游客了。 当游客都走了,账结算完 超市晚上才正式歇业(终止).
类比回线程池,那么线程池关闭,不能提交任务,会将正在处理(不包含队列中)的任务执行完。 线程所有完成以后 进入终止状态。
Callable 是在java.util.concurrent 包中新接口,若是要实现线程, 实现Runnable 和Callable 接口均可以,
区别在于, runnable 没有返回值,不抛出异常,one way的方式,因此使用runnable的时候,异常基本上都是线程内部处理,不可以交给主线程来处理,
甚至有实现,这个线程挂了或有没有执行完都不知道。
Callable 摆脱了前面说的这种状况,它加了范性的返回值,同时容许抛出异常,这样对多线程的调度和处理更加灵活一些。
Future是JDK内置的并发设计模式中的Future模式,有兴趣的同窗能够做为扩展阅读深刻了解一下,这个经典模式。
这里简单的介绍一下:
通常状况,若是要拿到方法的返回值 是否是要等方法运行完,若是这个方法要运行好久,那岂不是要等好久。
那开子线程不是不用等了吗?非也,线程在计算完以前,可能这个结果是个null,对你后面的逻辑也是有影响的。
因此就引入了FutureData的概念,子线程计算的结果 对我来讲是一个FutureData, 我只是须要知道 它计算完毕以后我去取一下数据就能够了。
或者你能够这么认为,这个Future对象是一个中介,它持有对线程计算结果的引用同时也有线程计算完成的状态,你以后问一下中间人,计算完了吗?
完了 那么把结果给我。
扒一扒ThreadPoolExecutor
用Executors建立线程池的时候 经过IDE点进那个方法进去 你会发现,哇塞 都是调用了同一个类的构造方法。That is ThreadPoolExecutor.
ThreadPoolExecutor 的构造方法是被重载的,整体归纳起来,须要设定这么几个参数
corePoolSize : 指定线程池 常驻线程的数量,
maximumPoolSize: 指定线程池 最大的线程数量
keepAliveTime: 容许线程最大空闲时间, 若是当线程数大于corePoolSize的时候 会释放空闲的线程来节约内存。
TimeUnit: 空闲时间的单位
BlockingQueue<Runnable> 任务队列, 提交到线程池的任务就是放在这的。
ThreadFactory 线程建立工厂用来建立线程
RefectedExecutionHandler : 主要用于 当须要处理 任务队列满了以后 拒绝提交状况下的处理。
逻辑图:
这里须要具体说说 这个任务队列,构造函数里面的声明的是,BlockingQueue, 这是个接口 根据功能不一样能够运用好几种不一样的队列。
前文提到过不少次决绝策略, 这究竟是什么鬼呢。 其实也很好理解 就是任务队列满了以后 须要处理下后续请求,说是说拒绝策略,也未必真的就把人家个拒绝了。
JDK内置了4种策略
回看Executors创建的几个线程池:
Executors在建线程池的时候,就是在这个几个参数的上作选择,以实现不一样类型的线程池
WorkQueue | corePoolSize | MaximumPoolSize | aliveTime | Reject Handler | |
singleThreadPool | LinkedBlockingQueue | 1 | 1 | 0 second | AbortPolicy |
FixedThreadPool |
LinkedBlockingQueue | 定义的线程数 | 定义的线程数 | 0 second | AbortPolicy |
cachedThreadPool | SynchronousQueue | 0 | Integer.MAX_VALUE | 60 second | AbortPolicy |
JDK 默认的Policy 是AbortPolicy ,尽管没有传递这个参数,JDK 默认采用终止策略,
有意思的是,cachedThreadPool 用的是SynchronousQueue,即来了请求就开线程,线程空闲60秒就销毁。
欢迎关注微信公众号