Java自定义线程池详解

一、线程池介绍

顾名思义,线程池就是管理一系列线程的资源池,其提供了一种限制和管理线程资源的方式。每个线程池还维护一些基本统计信息,例如已完成任务的数量。
总结一下使用线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

线程池一般用于执行多个不相关联的耗时任务,没有多线程的情况下,任务顺序执行,使用了线程池的话可让多个不相关联的任务同时执行提高执行的效率。

 

二、线程池的执行流程

  1. 当任务提交到线程池时,假如当前线程数小于核心线程数,直接创建线程执行,并且不会销毁,直到达到核心线程数。
  2. 当核心线程都在执行但是还有任务提交时,任务放在阻塞队列中。
  3. 阻塞队列也满了以后,继续创建线程执行任务,直到达到最大线程数。
  4. 最大线程也满了以后,执行设定的的拒绝策略。
  5. 当线程空闲下来以后,线程在达到线程空闲等待时间后销毁,直至数量降低至核心线程数。

 

三、线程池创建的方式 

方式一:通过 Executor 框架的工具类 Executors 来创建。

我们在idea中查看Executors这个类,按住Ctrl+F12查看它的所有方法,框起来的方法就是它创建线程池的方法。

可以看出,通过Executors工具类可以创建多种类型的线程池,包括:

  • FixedThreadPool固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
  • SingleThreadExecutor只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程执行完毕后,按先进先出的顺序执行队列中的任务。(注意:每次就只有一个线程执行。)
  • CachedThreadPool:可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  • ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池。
  • SingleThreadScheduledExecutor:此线程池就是单线程ScheduledThreadPool
  • WorkStealingPool:这个是 JDK1.8 版本加入的一种线程池,stealing 翻译为抢断、窃取的意思,它的线程池和上面5种都不一样,用的是 ForkJoinPool 类。(ForkJoinPool和ThreadPoolExecutor都实现了Executor和ExecutorService接口, 但具体实现不同, 这里暂时不展开) 

方式二:通过ThreadPoolExecutor构造函数来创建(推荐)

《阿里巴巴Java开发手册》中强调,线程池不允许使用Executors去创建,而是通过 ThreadPoolExecutor 的方式。在手册上截取下来规范如下:

图中提到的OOM,即Out Of Memory(内存溢出),原因是使用Executors类创建的线程池,没有限制任务队列的长度或创建线程的数量,如果大量的请求进来,就有可能会导致OOM。


那么接下来看看ThreadPoolExecutor构造函数中的参数,了解应该怎么使用它 

  • corePoolSize:线程池核心线程数,任务队列未达到队列容量时,最大可以同时运行的线程数量。
  • maximumPoolSize:线程池最大线程数,任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
  • keepAliveTime:线程保持活跃而不销毁的时间,线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁。
  • unit:TimeUnit类型,作为keepAliveTime的时间单位
  • workQueue:工作队列,新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。workQueue类型为BlockingQueue,jdk已经内置了几种实现,通常会取以下几种类型:

ArrayBlockingQueue:有界队列,基于数组的先进先出队列,此队列创建时,需要指定大小
LinkedBlockingQueue:无界队列,基于链表的先进先出队列,如果创建时没有指定队列大小,则默认为Integer.MAX_VALUE。它其实间接的造成maximumPoolSize失效,同时也存在内存溢出的隐患
SynchronousQueue:同步队列,任务不用排队而是直接提交的队列,该队列下任务过来会直接新建线程执行
PriorityBlockingQueue:和ArrayBlockingQueue差不多,不同的是它可以实现自动扩容,其中的元素要实现Comparatable接口,在创建时要传入comparator,这些都是用于元素的排序

  • threadFactory:线程工厂,顾名思义,就是用来生产线程的,它可以为线程设置一些属性,比如线程名、守护线程等,它还可以设置在主线程中对于子线程的未捕获异常的处理策略等等。
  • handler:表示拒绝执行任务的策略

它是RejectedExecutionHandler类型的引用,它会在队列饱和或者线程池被关闭时被调用。RejectedExecutionHandler接口在ThreadPoolExecutor中有四种类型的内部实现类:

ThreadPoolExecutor.AbortPolicy(默认):丢弃任务,并抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy:它既不会抛弃任务,也不会抛出异常,而是将任务回退给调用者,使用调用者的线程来执行任务
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中存在最久未被执行的任务,然后重新尝试execute执行,但如果线程池被关闭,任务会直接被丢弃
ThreadPoolExecutor.DiscardPolicy:丢弃任务,不会抛出异常


 了解完ThreadPoolExecutor的构造函数后,我们在回过头来看Executors类中创建线程池的方法

(1) 以newFixedThreadPool() 方法为例
可以看到它的底层其实就是ThreadPoolExecutor,传入一个参数,设置核心线程数和最大线程数为同一个值,因为将二者设置成一样的值,所以keepAliveTime设置为0,因为没有非核心线程的概念,所以自然也不需要设置存活时间,最后传入LinkedBlockingQueue作为任务队列,我们点开它的构造函数发现

 

LinkedBlockingQueue的默认长度是Integer.MAX_VALUE,21亿多,基本可以当做无限大了,这时候如果很多请求进来,线程池不会启用拒绝策略,而是在核心线程数满了之后,不断的插入任务队列中,这样就很有可能导致内存溢出。 

(2) 再看看newCachedThreadPool() 方法

CachedThreadPool是一个会根据需要创建新线程的线程池,可以看到它设置核心线程数为0,但最大线程数设置为Integer.MAX_VALUE,表示可能会不断的创建新线程,所以它设置每个线程存活时间为60S,最后传入一个同步队列,同步队列没有容量,也就是说,使用这种方式创建的线程池,只要有新请求进来,如果有空闲线程,则使用空闲线程来处理;否则就新建一个线程来处理任务。可以理解为CachedThreadPool的线程数是可以无限扩展的,可能会创建大量线程,从而导致内存溢出。 

四、总结:

在需要使用到线程池的时候,使用ThreadPoolExecutor来创建,建议是不同的业务使用不同的线程池,配置线程池的时候根据当前业务的情况对当前线程池进行配置,因为不同的业务的并发以及对资源的使用情况都不同,根据具体的业务场景进行自定义配置才是正解。

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一言^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值