在java中,使用线程时通过new Thread实现很简单,但是如果并发数量很多时,频繁地创建线程就会大大降低系统的效率。
所以可以通过线程池,使得线程可以复用,每执行完一个任务,并不是被销毁,而是可以继续执行其他任务。
花了两天时间去看了高洪岩写的《JAVA并发编程》,是想要知其然,知其所以然,在使用的情况下,了解学习了一下原理记录下java.util.concurrent并发包下的ThreadPoolExecutor特性和实现
使用示例
粗暴点,我们直接看如何使用吧
(一)使用Executors
|
|
通过该Executors的静态方法进行线程池的创建,而且从具体实现来看,还是调用了new ThreadPoolExecutor(),只是内部参数已经帮我们配置好了。
(二) 使用ThreadPoolExecutor
既然真正实现都是用ThreadPoolExecutor,那就自己设定好方法的参数吧。
|
|
打印效果如下:
|
|
任务Task提交之后,由于是多线程状态下,所以打印效果并不是同步的,可以看出任务都已经顺利执行。
我这个实现参数是5个corePoolSize核心线程数和5个maximumPoolSize最大线程数,当线程池中的线程数超过5个的时候,将新来的任务放进缓存队列中,小伙伴可以试下把任务数(for循环的个数)提高一点,让缓存等待的任务数超过5个,看看默认的任务拒绝策略(AbortPolicy)会抛出什么错误hhh
下面来看看ThreadPoolExecutor的庐山真面目吧~
ThreadPoolExecutor
它有以下四个构造方法:
|
|
从构造方法可以看出,前三个方法最终都是调用第四个构造器进行初始化工作的。
参数解释:
- corePoolSize:池中保持的线程数,包括空闲的线程,也就是核心池的大小
- maximumPoolSize:池中锁允许最大线程数
- keepAliveTime:当线程数量超过corePoolSize,在没有超过指定的时间内不从线程池中删除,如果超过该时间,则删除
- unit:keepAliveTime的时间单位
- workQueue:执行前用来保存任务的队列,此队列只保存由execute方法提交的Runnable任务
workQueue(任务队列,是一个阻塞队列)
ArrayBlockingQueue:
|
|
LinkedBlockingDeque:(支持列头和列尾操作,pollFirst/pollLast)
|
|
从源码构造函数可以看到,不传参数的时候,默认阻塞队列中的大小是Integer.MAX_VALUE;
SynchronousQueue:
|
|
Array和Linked在传入大小小于0时将会报错,比较常用的是LinkedBlockingDeque和SynchronousQueue,线程池的排队策略与BlockingQueue有关。
ThreadFactory:线程工厂
主要用来创建线程,可以在newThread()方法中自定义线程名字和设置线程异常情况的处理逻辑。
举个🌰:
|
|
handler:拒绝策略
有以下四种:
- ThreadPoolExecutor.AbortPolicy:当任务添加到线程中被拒绝时,它会抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:任务被拒绝时,线程池丢弃被拒绝的任务
- ThreadPoolExecutor.DiscardOldestPolicy:任务被拒绝时,线程池会放弃等待队列中最旧的未处理文物,然后将被拒绝的任务添加到等待队列中
- ThreadPoolExecutor.CallerRunsPolicy:任务被拒绝时,会使用调用线程池的Thread线程对象处理被拒绝的任务
ThreadPoolExecutor继承结构
可以看出,实际上ThreadPoolExecutor是继承了AbstractExecutorService类和引用了ExecutorService、Executor接口。
AbstractExecutorService
|
|
AbstarctExecutorService是一个抽象类,它实现的是ExecutorService接口
ExecutorService
|
|
接口ExecutorService引用了Executor接口,Executor接口比较简单,只有一个execute方法定义
Executor
|
|
小结:
Executor是一个顶级接口,定义了一个execute方法,返回值为空,参数为Runnable。
ExecutorService继承了Executor并且定义了其它一些方法,结果如下图:
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法。
最后ThreadPoolExecutor继承了AbstractExecutorService,我们最常用到它两个方法,submit和execute,下面介绍一下这两者:
|
|
|
|
小结
execute()方法在ThreadPoolExecutor中进行了重写,submit()方法是在AbstractExecutorService实现的,ThreadPoolExecutor并没有重写,并且execute方法是没有返回结果的,submit的返回类型是Future,能够获得任务的结果,但是实际执行的还是execute方法。
当然,还有例如shutdown、getQueue、getActiveCount、getPoolSize等方法没有介绍到,推荐胖友们打开IDE进行查看吧~
ps:关于线程池的原理并未深入记录,有关它的任务拒绝策略、线程初始化、ThreadPoolExecutor构造之后,当任务超过设定值,它的执行策略等原理都值得去深入学习,下回记录~