为什么要有线程池
1 资源有限
没有用线程池的情况下,需要对每个任务创建一个线程,如果任务较多或线程执行较慢,线程会迅速增加,耗尽内存资源,进而导致服务崩溃。为了更合理的利用资源,需要对线程进行管理,为了避免任务过多而引起的服务异常,需要对任务进行管理
2 线程开销
线程每次创建和销毁时,都会有时间成本,为了提高性能,需要对已创建的线程进行复用。
使用线程池的目的总结来说,就是充分利用系统资源,同时避免过度调度引起的开销。
线程池该怎么用
1 线程池创建
1.1 Executors方式
- Executors.newFixedThreadPool:核心线程数一旦指定则固定,核心线程数和最大线程数相等,任务队列队列使用的是未指定容量的链表阻塞队列,链表阻塞队列未指定容量时,默认为Integer最大值。
- Executors.newCachedThreadPool:使用SynchronousQueue作为任务队列,新增和删除任务的操作为offer/poll的方式,最大线程数为Integer最大值,对于任意一个任务,每次都会创建一个新的线程来执行它。
- Executors.newSingleThreadExecutor:核心线程数和最大线程数都是1,任务队列队列使用的是未指定容量的链表阻塞队列,链表阻塞队列未指定容量时,默认为Integer最大值。
- Executors.newSingleThreadExecutor:创建一个定时任务线程池,核心线程数由输入参数指定,最大线程数为Integer.max,任务队列为延时阻塞队列,适用于在未来的某个时间点做某个任务。
- Executors.newWorkStealingPool:jdk8引入,内部会构建ForkJoinPool,利用working-stealing算法,并行的处理任务,但是不保证处理顺序。Fork/Join框架,把大任务分解成多个小任务,每个小任务执行完成之后,再合并成最终结果。work-stealing算法:某个线程从其他线程队列里窃取任务来执行。
1.2 ThreadPoolExecutor方式(推荐)
ExecutorService executorService = new ThreadPoolExecutor (corePoolSize,maxPoolSize,keepAliveTime,unit,任务队列,ThreadFactory,RejectedExecutionHandler);
建议使用ExecutorService作为静态类型,可以处理不带返回值的任务(execute)和带返回值的任务(submit),ThreadPoolExecutor接口默认无返回值,
- corePoolSize:核心线程数,当前线程数小于核心线程数时,创建新线程(充分利用资源),将当前任务作为线程的第一个任务来执行
- 任务队列:当前线程数大于核心线程数,将任务插入到队列,排队等待,线程会不断消费任务队列里的任务,
- maxPoolSize:最大线程数,当任务队列满了,且线程数小于最大线程数且小于最大容量(2^29-1),创建新线程
- keepAliveTime:如果线程数大于核心线程数,且空闲时长超过keepAliveTime,则销毁线程
- ThreadFactory:线程创建工厂,线程池会根据工厂方法来创建线程,通过工厂方法,自定义线程的名称、优先级及其他特性
- RejectedExecutionHandler:拒绝策略,当线程池饱和而无法继续执行任务时,采用拒绝策略来处理新任务。
- AbortPolicy:拒绝任务并抛出异常
- DiscardPolicy:拒绝任务但不抛出异常
- DiscardOldestPolicy:丢弃队列中最老的任务,并执行当前任务
- CallerRunsPolicy:主线程执行任务
2 执行任务
1 执行流程![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/06b13c1b66579b534b656bdb01ba780e.png)
2 任务执行
- 获取任务:若是核心线程,使用take()方法从队列获取任务,执行完任务后,继续自旋从任务队列获取任务;若是非核心线程,使用poll(keepAliveTime)来获取任务,当执行完第一个任务后,会keepAliveTime超时,返回空任务;
- 执行任务:由woker线程调用任务的run方法
3 线程池关闭
- shutDown:将线程池状态设为关闭(ShutDown),停止接收新的任务,终止空闲的线程,正在运行的或者等待中的任务将会继续执行
- shutDownNow:将线程池状态设为停止,停止接收新的任务,终止(interrupt)所有线程,等待中的任务将不会继续执行,会返回未执行完的任务列表
线程池怎么实现的
1 线程池状态图
- Running:线程池正常运转,接收新的任务
- ShutDown:不接收新任务,队列中的任务和正在运行的任务会继续运行
- ShutDownNow:不接收新任务,队列中的任务不会运行,中止正在运行中的任务
- Tiding:线程池数量为空,队列任务为空时
- Terminated:terminated()方法执行完成之后
2 任务管理
使用阻塞队列来管理任务
3 线程管理
线程池数据结构
Java使用HashSet来管理线程(Worker类),那么HashSet如何唯一确定一个Worker呢?
Worker类并没有重写hashcode和equals,使用的是Object中默认的hashcode和equals方法,默认的equals方法是通过==运算符来判断,hashcode则为native方法,跟对象的monitor相关,有兴趣的可以去查看openjdk源码,hashcode方法可以保证对于同一个对象,hashcode方法会返回同一个值。
线程新增/删除
线程池为共享资源,对于它的操作需要保证线程安全,Java使用ReentrantLock锁来保证,任何对线程池的操作,首先要获取到锁。