目录
1.线程池概述
1.1 线程池产生的背景
- 当我们去创建每个线程的时候,都需要为它去分配内存,如虚拟机栈,程序计数器,本地方法栈等,所以创建的过程时间消耗是比较大的
- 当线程使用结束,如run方法执行结束,结束的时候,又要进行垃圾回收,又是大的时间消耗
- 当再执行到另一个单元的时候,又需要多个线程的时候,又重新经历从创建线程,到使用完销毁线程的过程,重复创建和销毁的这两个过程导致效率上的消耗
- 所以,我们思考有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务,所以就诞生了线程池
1.2 什么是线程池?
- 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
线程池的工作原理:
- 线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果任务数量超过了线程的最大数量,超出数量的任务排队等候,等其他某个任务执行完毕,归还回线程池,再从线程池中获取线程,执行任务
1.3 使用线程池的好处
合理利用线程池能够带来三个好处:
- 1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁的消耗。
- 2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会影响系统稳定性,使用线程池可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 即使用线程池可以进行统一的分配,调优和监控
1.4 线程池的主要特点
- 线程复用
- 控制最大并发数
- 管理线程
2.自己实现一个线程池
2.1 实现线程池的通用规则
我们先来看一下,自己实现一个线程池有哪些通用的规则?
- 1.任务队列
- 提交的任务,存放在任务队列中
- 2.拒绝策略
- 向任务队列提交任务的时候不能无限多,当处理不过来了,就要通过一定方式进行拒绝
- 实现拒绝策略有以下方式
- 抛出异常,
- 直接丢弃,
- 阻塞,
- 放到临时队列
- 3.初始化值init(min)
- 4.活跃值active
- 5.线程池中最大个数max
- 线程池不能一味增大,否则会栈溢出
- min <= active <= max
- 比如开始的时候创建两个线程(min=2),如果两个线程不够执行任务,那就尝试扩充一下线程,扩充到active,如果active还是满足不了,就扩充到最大值max,如果最大值满足不了,里面的任务就加到任务队列中,如果任务队列都放不下,那就执行决绝策略
- 当过了一个顶峰期(即一下子来了大量任务,让线程使用到了max),活跃线程就没那么频繁了,不能让应用一直维持在max的活跃线程,应用的上下文切换也是不小的损失,所以就需要减少线程
- 6.一些提供线程池的还提供异步任务和批量任务
2.2 实现代码
3.线程池的架构
- Executor:线程池的最上层接口,只有一个execute(Runnable)方法,用于提交任务
- ExecutorService:提供了线程池管理的上层接口,如线程池的销毁、任务提交、异步任务提交。
- ScheduledExecutorService:提供任务定时或周期执行方法的 ExecutorService。
- AbstractExecutorService:为 ExecutorService 的任务提交方法提供了默认实现。
- ThreadPoolExecutor:线程池的核心类,提供线程和任务的调度策略。我们大量时间会与这个类打交道
- ScheduledThreadPoolExecutor:属于线程池的一种,它可以允许任务延迟或周期执行,类似java的Timer。
- ForkJoinPool:JDK1.7加入的成员,也是线程池的一种。只允许执行 ForkJoinTask 任务,它是为那些能够被递归地拆解成子任务的工作类型量身设计的。其目的在于能够使用所有可用的运算资源来提升应用性能。
- Executors:创建各种线程池的工具类,通过命名我们想到了Object的工具类是Objects,Array的工具类是Arrays,Collection的工具类是Collections,所以线程池Excutor的工具类是Excutors