jdk自带线程池(jdk自带线程池有哪几种有什么区别)
导语:从源码看JDK提供的线程池,打开你脑袋壳
一丶什么是线程池(1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些数量多且时间短暂的任务,会导致频繁获取和释放连接,这样使得处理事务的效率大大降低,多以我们创建一个连接池,里面放了指定数目的连接,当应用需要数据库连接的时候去里面获取,使用完毕后再放到连接池里,这样就避免了重复的获取连接和释放连接,至于要获取什么样的连接池我们可以根据应用的特征,设置参数来决定。(2)线程池和连接池很相似,线程池的产生是为了避免重复的创建线程和回收线程。本着存在即合理,存在即有优点的理念(这个说法不普遍适用),线程池有如下三个优点:
①降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。②提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。③提高线程的可管理性。线程是稀缺资源,如果入限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
二丶ThreadPoolExecutor的使用ThreadPoolExecutor是线程池的最核心的一个类,所以要了解线程池我们先来看看ThreadPoolExecutor类的实现。本着先学开车后学修车的理念,我们先通过范例来学习一下ThreadPoolExecutor的使用(以后对JDK源码框架的学习都会本着这个原则)。
输出:
以上的范例就是ThreadPoolExecutor的简单应用,首先需要创建一个任务类MyTask ,其次在主方法里创建ThreadPoolExecutor对象,接着用for循环来模拟运行多个线程,然后execute方法执行,最后调用shutdown方法结束。
上面代码的实现和我们往常实现多线程有些区别,我们往常使用:
来创建一个线程执行任务,在应用ThreadPoolExecutor时,我们不再自己创建,而是使用线程池为我们创建的线程。
在创建线程池ThreadPoolExecutor对象时,有很多个构造参数,通过注释我们可以了解到,这些参数就是用来设置线程池的特征的。
三丶从源码来看ThreadPoolExecutor1)ThreadPoolExecutor结构:
2)线程池处理任务处流程:
知道了ThreadPoolExecutor的继承关系后我们来了解一下ThreadPoolExecutor的设计结构和思想,这对我们后面理解ThreadPoolExecutor的源码有很大的帮助:
(对队列的判断应该在线程池内部)
上面的这张图应该就能大概的描述ThreadPoolExecutor的实现了,同时也能够理解ThreadPoolExecutor的构造参数了。
线程池在创建的时候会设置CorePoolSize,maximumPoolSize,workQueue等几个重要参数,CorePoolSize指的是核心线程池的大小,maximumPoolSize指的是线程池的线程数最大值,workQueue为线程池指定的阻塞队列。处理流程:
主线程执行execute方法,提交任务到线程池,线程池判断核心线程池中的线程是否都在工作,如果不是则创建一个线程来执行新任务,如果都在工作,进入下一步。判断工作队列是否已满,如果不满,则将新任务加入到阻塞队列中,如果满了进入下一步。判断线程池中线程数是否小于maximumPoolSize,如果小于,创建新的线程来处理新任务,否则交给饱和策略。3)源码:接下来我们跟着方法的执行流程来跟源码:源码从哪里开始跟?当然是从execute方法开始啦,毕竟这个是执行的开端呀(博主跟源码还是喜欢这样,这样慢慢跟下去,不太喜欢直接看构造,字段,方法,等看到了字段属性再跟下去看)。
3.1 execute:
相信这个方法已经不要我再多作什么赘述了吧(捂嘴笑.jpg)。
这里我们再将ThreadPoolExecutor的一些字段列一下:
如果没有对照源码看博文的小伙伴可能会有很多疑问,相信你打开源码再看我的方法注释会更好的理解!
3.2 addWorker:看了上面的execute方法,相信我们可以看出来addWorker方法也算是核心了,addWorker方法担任了所有的将任务交给线程的操作:
从上面的代码我们可以发现,当任务交给线程执行的时候并不是直接的交给线程,线程池创建线程后会将线程封装成工作线程Worker,Worker工作完后还会继续去工作队列中获取任务来执行。
3.3 Worker类:
我们可以从下面这段Worker类的Run方法中窥到这一点:
jdk1.8和1.7有很大的改动,有兴趣的朋友可以去对比一下
3.4 submit:
既然讲到了execute方法,怎么能少了submit方法,当我们执行一个任务的时候,有的时候需要返回值,这个时候我们就需要用到submit方法了。
其实我们通过源码可以发现submit方法内部也是调用execute方法,当调用submit方法的时候我们可以收到一个Future对象,我们可以调用Future对象的get方法来获得它的返回值。
注:我在ThreadPoolExecutor中找submit方法的时候没找到,然后才发现ThreadPoolExecutor是直接继承他的父类AbstractExecutorService的。
从上面的代码我们可以看出将任务包装成一个RunnableFuture对象,然后将这个对象用execute执行。如果看过我上面推荐的博文,读者应该知道Callable和和Runnable的区别了:是否有返回值。
关于这个Future的结构我们来理一下:FutureTask→(实现)RunnableFuture→(继承)Runnable,Future
我们可以看看newTaskFor方法:
它的底层其实就是new一个FutureTask对象而已,所以FutureTask才是真正的实现类哦!关于FutureTask的实现,我们会专门抽出时间去整理!
3.5shutdown和shutdownNow:我们可以通过shutdown和shutdownNow方法来关闭线程池,shutdown方法通过遍历工作线程HashSet,将运行状态(ctl)这设置为SHUTDOWN并调用interrupt方法中断所有线程,shutdownNow同样遍历所有线程,将将运行状态(ctl)这设置为STOP,并调用interrupt方法中断所有线程。
3.6 RejectedExecutionHandler:最后我们来看看这个饱和策略,当线程和队列都满了过后,表明这个线程池处于饱和的状态,那么我们必须要采取一定的措施来处理这些任务,在默认的情况下我们会执行AbortPolicy,表示无法处理任务,抛出异常。同时JDK提供了以下的几种策略:
AbortPolicy:直接抛出异常CallerRunsPolicy:用调用者的线程来执行任务DiscardOldestPolicy:丢弃队列里最近的任务,并执行这个任务DiscardPolicy:丢弃,不处理我们也可以实现RejectedExecutionHandler接口进行自定义操作,例如有的时候我们需要将这种异常记录到日志当中,这个时候我们就需要自定义了!
总结:通过对线程池的学习,自己又了解到不少自己不知道的知识,例如Future接口等,也通过学习,发现一些容器和锁真的很常见,自己平时应用的时候很少碰见,当然,大师的编程水平也值得我们这些小民学习!
本文内容由小萱整理编辑!