什么是线程池
Java 中的线程池是运用场景最多的并发框架, 几乎所有需要异步或并发执行任务的程序都可以使用线程池.
在开发过程中, 合理地使用线程池能够带来 3 个好处.
第一: 降低资源消耗. 通过重复利用机制已降低线程创建和销毁造成的消耗.
第二: 提高响应速度. 当任务到达时, 任务可以不需要等到线程创建就能立即执行.
第三: 提高线程的可管理性. 线程是稀缺资源, 如果无限制地创建, 不仅会消耗系统资源, 还会降低系统的稳定性, 使用线程池可以进行统一分配, 调优和监控. 但是, 要做到合理利用线程池, 必须对其实现原理了如指掌.
线程池作用
线程池是为突然大量爆发的线程设计的, 通过有限的几个固定线程为大量的操作服务, 减少了创建和销毁线程所需的时间, 从而提高效率.
如果一个线程的时间非常长, 就没必要用线程池了(不是不能作长时间操作, 而是不宜.), 况且我们还不能控制线程池中线程的开始, 挂起, 和中止.
线程池的分类
ThreadPoolExecutor
Java 是天生就支持并发的语言, 支持并发意味着多线程, 线程的频繁创建在高并发及大数据量是非常消耗资源的, 因为 java 提供了线程池. 在 jdk1.5 以前的版本中, 线程池的使用是及其简陋的, 但是在 JDK1.5 后, 有了很大的改善. JDK1.5 之后加入了 java.util.concurrent 包, java.util.concurrent 包的加入给予开发人员开发并发程序以及解决并发问题很大的帮助. 这篇文章主要介绍下并发包下的 Executor 接口, Executor 接口虽然作为一个非常旧的接口(JDK1.5 2004 年发布), 但是很多程序员对于其中的一些原理还是不熟悉, 因此写这篇文章来介绍下 Executor 接口, 同时巩固下自己的知识. 如果文章中有出现错误, 欢迎大家指出.
Executor 框架的最顶层实现是 ThreadPoolExecutor 类, Executors 工厂类中提供的 newScheduledThreadPool,newFixedThreadPool,newCachedThreadPool 方法其实也只是 ThreadPoolExecutor 的构造函数参数不同而已. 通过传入不同的参数, 就可以构造出适用于不同应用场景下的线程池, 那么它的底层原理是怎样实现的呢, 这篇就来介绍下 ThreadPoolExecutor 线程池的运行过程.
1.corePoolSize
线程池核心线程数, 默认情况下, 核心线程会在线程池中一直存活, 即使它们处于闲置状态. 如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为 true, 那么闲置的核心线程在等待新任务到来时会有超时策略, 这个时间间隔由 keepAliveTime 所指定的时长后, 核心线程就会被终止.
2.maximumPoolSize
线程池所能容纳的最大线程数, 当活动线程达到这个数值后, 后续的新任务将被阻塞.
3.keepAliveTime
非核心线程闲置时的超时时长, 超过这个时长, 非核心线程就会被回收. 当 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为 true 时, keepAliveTime 同样会作用于非核心线程.
4.unit
keepAliveTime 参数的时间单位.
5.workQueue
执行前用于保持任务的队列. 此队列仅保持由 execute 方法提交的 Runnable 任务.
线程池四种创建方式
Java 通过 Executors(jdk1.5 并发包)提供四种线程池, 分别为:
newCachedThreadPool 创建一个可缓存线程池, 如果线程池长度超过处理需要, 可灵活回收空闲线程, 若无可回收, 则新建线程.
newFixedThreadPool 创建一个定长线程池, 可控制线程最大并发数, 超出的线程会在队列中等待.
newScheduledThreadPool 创建一个定长线程池, 支持定时及周期性任务执行.
newSingleThreadExecutor 创建一个单线程化的线程池, 它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序 (FIFO, LIFO, 优先级) 执行.
newCachedThreadPool
创建一个可缓存线程池, 如果线程池长度超过处理需要, 可灵活回收空闲线程, 若无可回收, 则新建线程. 示例代码如下:
// 无限大小线程池 jvm 自动回收 ExecutorServicenewCachedThreadPool= Executors.newCachedThreadPool(); for(inti= 0;i< 10;i++) { finalinttemp=i; newCachedThreadPool.execute(newRunnable() { @Override publicvoidrun() { try{ Thread.sleep(100); }catch(Exceptione) { //TODO: handle exception } System.out.println(Thread.currentThread().getName() +",i:"+temp); } }); } |
总结: 线程池为无限大, 当执行第二个任务时第一个任务已经完成, 会复用执行第一个任务的线程, 而不用每次新建线程.
newFixedThreadPool
创建一个定长线程池, 可控制线程最大并发数, 超出的线程会在队列中等待. 示例代码如下:
ExecutorServicenewFixedThreadPool= Executors.newFixedThreadPool(5); for(inti= 0;i< 10;i++) { finalinttemp=i; newFixedThreadPool.execute(newRunnable() { @Override publicvoidrun() { System.out.println(Thread.currentThread().getId() +",i:"+temp); } }); } |
总结: 因为线程池大小为 3, 每个任务输出 index 后 sleep 2 秒, 所以每两秒打印 3 个数字.
定长线程池的大小最好根据系统资源进行设置. 如 Runtime.getRuntime().availableProcessors()
newScheduledThreadPool
创建一个定长线程池, 支持定时及周期性任务执行. 延迟执行示例代码如下:
ScheduledExecutorServicenewScheduledThreadPool= Executors.newScheduledThreadPool(5); for(inti= 0;i< 10;i++) { finalinttemp=i; newScheduledThreadPool.schedule(newRunnable() { publicvoidrun() { System.out.println("i:"+temp); } }, 3, TimeUnit.SECONDS); } |
表示延迟 3 秒执行.
newSingleThreadExecutor
创建一个单线程化的线程池, 它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序 (FIFO, LIFO, 优先级) 执行. 示例代码如下:
ExecutorServicenewSingleThreadExecutor= Executors.newSingleThreadExecutor(); for(inti= 0;i< 10;i++) { finalintindex=i; newSingleThreadExecutor.execute(newRunnable() { @Override publicvoidrun() { System.out.println("index:"+index); try{ Thread.sleep(200); }catch(Exceptione) { //TODO: handle exception } } }); } |
注意: 结果依次输出, 相当于顺序执行各个任务.
线程池原理剖析
提交一个任务到线程池中, 线程池的处理流程如下:
1, 判断线程池里的核心线程是否都在执行任务, 如果不是 (核心线程空闲或者还有核心线程没有被创建) 则创建一个新的工作线程来执行任务. 如果核心线程都在执行任务, 则进入下个流程.
2, 线程池判断工作队列是否已满, 如果工作队列没有满, 则将新提交的任务存储在这个工作队列里. 如果工作队列满了, 则进入下个流程.
3, 判断线程池里的线程是否都处于工作状态, 如果没有, 则创建一个新的工作线程来执行任务. 如果已经满了, 则交给饱和策略来处理这个任务.
来源: http://www.bubuko.com/infodetail-2992089.html