目录
1.线程池的优点:
2.线程池的实现原理:
2.1 ThreadPoolExecutor执行示意图
3.线程池的使用
4.向线程池提交任务
5.关闭线程池
6.线程池提供方法可以重写
7.监控线程池,自定义线程池
1.线程池的优点:
线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。
2.线程池的实现原理:
提交一个任务到线程池中,线程池的处理流程如下:
1> 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2> 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3> 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
2.1 ThreadPoolExecutor执行示意图
上图说明:
1、如果线程池中的线程数量少于corePoolSize,就创建新的线程来执行新添加的任务
2、如果线程池中的线程数量大于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到workQueue中
3、如果线程池中的线程数量大于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务
4、如果线程池中的线程数量超出了maximumPoolSize,就用RejectedExecutionHandler.rejectedExecution()方法来执行拒绝策略
总结:ThreadPoolExecutor采取上述的总体设计思路,就是为了在执行execute方法时,尽可能地避免获取全局锁,当运行的线程数大于等于corePoolSize时,几乎所有的excute()都会执行步骤2,而步骤2而不需要获取全局锁,步骤3是需要加锁创建的。
源码:
3.线程池的使用
我们可以通过ThreadPoolExecutor来创建线程池
ThreadPoolExecutor有多个构造器,不一定全部参数都包含,没有包含的使用默认值,以下是参数说明:
- corePoolSize:核心线程池的线程数量
- maximumPoolSize:最大的线程池线程数量
- keepAliveTime:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间,一般都是0
- unit:线程活动保持时间的单位。
- workQueue:指定任务队列所使用的阻塞队列,当然不仅仅只有下面三个阻塞队列,有优先级的可以考虑PriorityBlockingQueue,建议使用有界队列,否则后面无界的话不停的创建线程,容易撑爆内存
- ThreadFactory:线程工厂,主要用来创建线程;
- handler:当队列和线程池都满了,说明线程池处于饱和状态,那么必须对新提交的任务采用一种特殊的策略来进行处理,默认是AbortPolicy,这四个都是ThreadPoolExecutor的静态内部类,这几个类都是是实现RejectedExecutionHandler接口的hanler有以下四种取值:
我们现在用第四种策略来处理上面的程序例子:
执行结果:
线程池中活跃的线程数: 1
线程池中活跃的线程数: 2
线程池中活跃的线程数: 2
----------------队列中阻塞的线程数1
线程池中活跃的线程数: 2
----------------队列中阻塞的线程数2
线程池中活跃的线程数: 2
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 3
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 4
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数3
线程池中活跃的线程数: 5
----------------队列中阻塞的线程数3
这里采用了丢弃策略后,就没有再抛出异常,而是直接丢弃。在某些重要的场景下,可以自定义处理策略,采用记录日志或者存储到数据库中,而不应该直接丢弃。
4.向线程池提交任务
有两个方法提交,一个是execute()方法和submit()方法。
ThreadPoolExecutor中实现的execute实现的方法见上面所示,接口Exeutor中定义如下:
AbstractExecutorService中的submit方法,有三种重载方法,源码:
5.关闭线程池
可以通过调用线程池的shutdown或者shutdownNow方法关闭线程池,他们的原理都是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止,两者的差别:
- shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或者暂停任务的线程,并返回等待执行任务的列表;
- shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法的任意一个,isShutDown方法就会返回true,当所有的任务都已经关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true,至于应该调用那一个方法,可以根据线程池的特性来决定,通常用shutdown方法调用关闭线程池,如果任务不一定执行完,则可以调用shutdownNow。
6.线程池提供方法可以重写
这几个是ThreadPoolExecutor提供的未实现的方法,可以通过重写他们在任务执行前,执行后和线程池关闭前执行一些代码来进行监控。
7.监控线程池,自定义线程池
一般我们创建线程池的时候,有两种选择,第一种是通过ThreadPoolExecutor创建,还有一种就是通过Executors创建。那么他们区别是什么呢,我们接下来分析:
Executors类和ThreadPoolExecutor都是util.concurrent并发包下面的类, Executos下面的newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor、newCachedThreadPool底线的实现都是用的ThreadPoolExecutor实现的,所以ThreadPoolExecutor更加灵活。
ThreadPoolExecutor
Executors
线程池通过Executors类的四种创建方式
- Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
一、newCachedThreadPool:由参数可以看出, 核心线程数为0,最大线程数很大,队列为无界阻塞队列,空闲存活时间为60秒,意思就是说,来个任务,如果没有空闲的线程,就创建个线程,如果有空闲的线程,就要空闲的线程。
二、newFixedThreadPool:由参数可以看出,这是创建一个固定大小的线程池,核心线程数和最大线程数一致,linkedBlockingQueue为无界阻塞队列,意思就是说我最多只能创建固定大小的线程来处理任务,比如核心线程数和最大线程数都为7,但突然来了12个任务,另外5个无法立刻执行,就放到了linkedBlockingQueue中,如果内存无限大,任务可以是无限多的,newFixedThreadPool中的keepAliveTime、unit都是无意思的,原因最大线程数没有超过核心线程数。
三、newScheduledThreadPool:由参数可以看出, 核心线程数一定,最大线程数很大,队列为优先级队列此线程池可以做定时执行。
三、newSingleThreadExecutor:由参数可以看出, 核心线程数和最大线程数都是1,linkedBlockingQueue为无界阻塞队列,也就是说是中只有一个线程在执行,来了再多个任务都得等,等线程执行完上个任务,再去执行下个任务。
自定义ThreadPoolExecutor
有界队列:根据上面参数可知,当队列为有界队列时,
corePoolSize < maximumPoolSize
corePoolSize < maximumPoolSize ,BlockingQueue满了,线程池会创建新的线程,直到maximumPoolSize 没满,没有触发拒绝策略,过了一段时间,任务减少了,一些线程空闲时间超过了keepAliveTime,就会被移除。
corePoolSize = maximumPoolSize
无界队列:
corePoolSize < maximumPoolSize,队列一直不会满(理想情况下),不会触发拒绝策略,如果一段时间没有任务时线程闲置时间超过keepAliveTime,会被移除线程池。