浅谈Java线程池ThreadPoolExecutor

1.线程的概念:


(1) 线程是程序执行最小单元;

(2)线程由线程ID、当前指令指针、寄存器集合和堆栈组成;

(3)线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源;

(4)一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行;

以上概念来自百度百科。


2.线程的实现:


(1)自定义一个类,去继承Thread类,重写run方法;

(2)自定义一个类,去实现Runnable接口,重写run方法;

(3)自定义一个类,实现Callable接口,重写call方法;

注意:

     ① Callable 规定的方法是 call(),而 Runnable 规定的方法是  run();

     ② Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的;

     ③ Callable实现 call() 方法可抛出异常,而 Runnable 重写 run() 方法是不能抛出异常的;

     ④ 运行Callable任务可拿到一个Future对象。


浅谈Java线程池ThreadPoolExecutor_第1张图片
自定义类实现Callable接口

3.线程池的概念:


(1)线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程(提高线程复用,减少性能开销);

(2)线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求;

(3)增加可用线程数量是可能的,即当可用线程不够时,会创建新的可用线程数量;

(4)线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中然后等待下一次分配任务;


4.使用线程的原因:


 (1)线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接使用而不用新建一个线程;

 (2) 线程池节省了CLR 为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源;

 (3)线程池根据当前在系统中运行的进程来优化线程时间片;

 (4)线程池允许我们开启多个任务而不用为每个线程设置属性;

 (5)线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用;

 (6)线程池可以用来解决处理一个特定请求最大线程数量限制问题;

    本质上来讲,我们使用线程池主要就是为了减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;节约应用内存(线程开的越多,消耗的内存也就越大,最后死机)


5.线程池的作用:


         线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。

        一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。


6.线程池代码起源之 Executor(执行者)


浅谈Java线程池ThreadPoolExecutor_第2张图片
Executor 源码


严格意义上Executor 并不是一个线程池,源码中只有一个execute 方法,所以Executor 只是一个执行线程的工具。

那么问题来了,明显不是我们想要的结果,线程池到底是什么呢???这个东西神秘背后是什么呢???

通过类的继承关系我们可以发现,Executor 有一个  ExecutorService  子接口。


浅谈Java线程池ThreadPoolExecutor_第3张图片
ExecutorService  源码与 API


一般说线程池接口,基本上就是在说这个 ExecutorService  ,里面有各种API(使用IDEA编辑器 Ctrl+O 能看到所有的API)供我们使用;

利用编译器,我们接着再往下查找 ExecutorService 实现类 ,然后出现一个抽象类  AbstractExecutorService


浅谈Java线程池ThreadPoolExecutor_第4张图片
抽象类 AbstractExecutorService


明显,这个抽象类也不是我们想结果,接着我们再往下查找 AbstractExecutorService 继承类,终于 最终的大BOSS ,

也就是我们最终想看的类 ThreadPoolExecutor  浮出水面 !!!


浅谈Java线程池ThreadPoolExecutor_第5张图片
ThreadPoolExecutor 



7.最终boss之 ThreadPoolExecutor  :


(1)ThreadPoolExecutor 之核心构造器:

浅谈Java线程池ThreadPoolExecutor_第6张图片
ThreadPoolExecutor 构造器

        参数说明:

        ①    corePoolSize: 核心线程数,线程池保留线程的数量,即使这些线程是空闲,如果 allowCoreThreadTimeOut 这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间( keepAliveTime),就会被销毁掉;

        ②    maximumPoolSize: 线程池最大允许的线程数;

        ③    keepAliveTime: 当当前的线程数大于核心线程数,那么这些多余的空闲的线程在被终止之前能等待新任务的时间;

        ④    unit:keepAliveTime 的时间的单位(MILLISECONDS - 毫秒 ;SECONDS - 秒;MINUTES -分;HOURS - 小时;DAYS - 天);

        ⑤    workQueue:这个是用来保留将要执行的工作队列;

        ⑥    threadFactory: 用于创建新线程的工厂(可选参数,默认DefaultThreadFactory );

        ⑦    handler:如果工作队列(workQueue)满了,那么这个handler是将会被执行(可选参数,提供AbortPolicy、DiscardPolicy、DiscardOldestPolicy 、CallerRunsPolicy 选择)

下面针对可选参数 threadFactory 、handler进行说明:

         threadFactory : 构造函数不带threadFactory,那么将默认使用java.util.concurrent.Executors.DefaultThreadFactory 创建出一个新的工厂对象,通过阅读源代码,主要是在创建新的线程的时候修改了线程名为  pool-全局线程池递增数编号-thread-当前线程池线程递增编号 ,让线程改为非守护线程,并设置线程的优先级为NORM_PRIORITY

        handler :    提供了4个默认值选择,它们分别是:

                ①  java.util.concurrent.ThreadPoolExecutor.AbortPolicy    这个是默认使用的拒绝策略,如果有要执行的任务队列已满,且还有任务提交,则直接抛出异常信息;

                ② java.util.concurrent.ThreadPoolExecutor.DiscardPolicy    这个是忽略策略,如果有要执行的任务队列已满,且还有任务提交,则直接忽略掉这个任务,即不抛出异常也不做任何处理;

                ③ java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy    忽略最早提交的任务.如果有要执行的任务队列已满,此时若还有任务提交且线程池还没有停止,则把队列中最早提交的任务抛弃掉,然后把当前任务加入队列中;

                ④ java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy    这个是来着不拒策略.如果有要执行的任务队列已满,此时若还有任务提交且线程池还没有停止,则直接运行任务的run方法;

下面我们来看一下具体的策略测试效果:

(2)例子1 使用默认的拒绝策略AbortPolicy:

浅谈Java线程池ThreadPoolExecutor_第7张图片
AbortPolicy 策略

运行结果:

浅谈Java线程池ThreadPoolExecutor_第8张图片
AbortPolicy策略运行结果

从结果看出来,可以看出线程是重复被使用的,而且当执行的任务超过工作队列的容量时,线程确实抛出了异常;

(3)例子2 使用忽略策略 DiscardPolicy:

将 RejectedExecutionHandler  handler  =  new  ThreadPoolExecutor.AbortPolicy();改为 RejectedExecutionHandler  handler  =  new  ThreadPoolExecutor.DiscardPolicy();

运行结果如下:

浅谈Java线程池ThreadPoolExecutor_第9张图片
DiscardPolicy策略运行结果

现在线程池正确退出了,而且也不抛出异常了,但是超过工作队列容量的任务全部被忽略了;

(4)例子3 使用忽略最早任务策略 DiscardOldestPolicy:

将 handler 改为    RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();

运行结果如下:

浅谈Java线程池ThreadPoolExecutor_第10张图片
DiscardOldestPolicy策略

从以上结果,我们可以看出除了客户0客户1、客户2刚好是3个核心线程被执行后,客户3客户9直接被忽略掉了;

(5)例子4 使用来着不拒策略 CallerRunsPolicy:

将拒绝策略改为RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

运行结果如下:

浅谈Java线程池ThreadPoolExecutor_第11张图片
CallerRunsPolicy策略

结果,我们可以发现所有的任务都被执行,而且竟然还有三个是在主线程执行的.现在明白我之前说的则直接运行任务的run方法的意思了吧,没错是直接调用run方法,而不是开启线程去执行任务.

(6)例子5 使用自定义的拒绝策略:

现在我们自己写一个拒绝策略,要求所有的任务都必须被线程池执行,而且都要在线程池中执行.

代码如下:

浅谈Java线程池ThreadPoolExecutor_第12张图片
自定义的拒绝策略

运行结果如下:

浅谈Java线程池ThreadPoolExecutor_第13张图片
自定义的拒绝策略运行结果

所有任务都被线程池执行了.而且我们自定义的拒绝策略也很简单,就是让工作队列调用put让其一直等待,直到有可用的容量存放任务.

从上面自定义的策略中我们可以看到,我们在handler中自定义处理了队列的调度工作,那么executor.getQueue() 这个方法的返回值我们通过源码查看,其实就是对工作队列 BlockingQueue workQueue 的操作;

下面我们具体看下BlockingQueue 的API 和 workQueue  的类型

(7)BlockingQueue中具体的API介绍:

        ① offer(E e): 将给定的元素设置到队列中,如果设置成功返回true, 否则返回false. e的值不能为空,否则抛出空指针异常;

        ② offer(E e, long timeout, TimeUnit unit): 将给定元素在给定的时间内设置到队列中,如果设置成功返回true, 否则返回false;

        ③ add(E e): 将给定元素设置到队列中,如果设置成功返回true, 否则抛出异常。如果是往限定了长度的队列中设置值,推荐使用offer()方法;

        ④ put(E e): 将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间;

        ⑤ take(): 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值;

        ⑥ poll(long timeout, TimeUnit unit): 在给定的时间里,从队列中获取值,如果没有取到会抛出异常;

        ⑦ remainingCapacity():获取队列中剩余的空间;

        ⑧ remove(Object o): 从队列中移除指定的值;

        ⑨ contains(Object o): 判断队列中是否拥有该值;

        ⑩ drainTo(Collection c): 将队列中值,全部移除,并发设置到给定的集合中;

(7)workQueue有以下四种队列类型:

        ①  SynchronousQueue(同步队列):这个队列接收到任务的时候,会直接提交给线程处理,而不保留它(名字定义为 同步队列)。但有一种情况,假设所有线程都在工作怎么办?

              这种情况下,SynchronousQueue就会新建一个线程来处理这个任务。所以为了保证不出现(线程数达到了maximumPoolSize而不能新建线程)的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大,去规避这个使用风险。 

        ②   LinkedBlockingQueue(链表阻塞队列):这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize;

        ③    ArrayBlockingQueue(数组阻塞队列):可以限定队列的长度(既然是数组,那么就限定了大小),接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误;

        ④    DelayQueue(延迟队列):队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务;


8.常见的几种线程:


        ①    newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;

浅谈Java线程池ThreadPoolExecutor_第14张图片
newFixedThreadPool 

注意:    在任何时候,有最多  nThreads(就是我们传入参数的数量)的线程将处理任务,nThreads 就是传入线程池的数量  ;当nThreads  <= 0 就会抛异常IllegalArgumentException;

②  newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;

浅谈Java线程池ThreadPoolExecutor_第15张图片
newCachedThreadPool

③    newScheduledThreadPool 创建一个定长任务线程池,支持定时及周期性任务执行;

浅谈Java线程池ThreadPoolExecutor_第16张图片
newScheduledThreadPool 

④    newSingleThreadExecutor 创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行;

浅谈Java线程池ThreadPoolExecutor_第17张图片
newSingleThreadExecutor 

     关于部分源码的分析就整理到这里了,希望对开发者有一点点小帮助吧

     这篇文章对您有开发or学习上的些许帮助,希望各位看官留下宝贵的star,谢谢。

另附上原贴两位作者的链接,

传送门一:骑小猪看流星  的   必须要理清的Java线程池(原创), 传送门 二 :天之妖星   的  java多线程之ThreadPoolExecutor

本人只是对其两位综合整理了一下,感谢两位作者


你可能感兴趣的:(浅谈Java线程池ThreadPoolExecutor)