为什么推荐使用线程池?
更多优秀文章,请扫码关注个人微信公众号或搜索“程序猿小杨”添加。
背景:
若并发的线程数量很多,且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
使用线程池可以解决上述问题:
1、为多线程执行琐碎的任务提供支持; (并发的线程数非常多,且每个执行时间很短就结束);
2、避免频繁线程创建、销毁过程的资源消耗;(因为频繁创建线程和销毁线程需要时间)
3、控制资源的使用,根据实际情况合理高效利用资源。(那么有没有一种办法使得线程可以复用)
使用线程池的好处:
1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2、提升响应速度:当任务到达时,不需要等到线程创建就能立即执行。
3、对线程做统一管理:线程是稀缺资源,若无限制创建,不仅消耗系统资源,还会降低系统稳定性,使用线程池可以统一分配、调优和监控。
核心ThreadPoolExecutor类:
1、使用JDK线程池ThreadPoolExecutor;
2、使用Spring包下ThreadPoolTaskExecutor多线程。
ThreadPoolExecutor提供的构造函数:
7.1、corePoolSize:该线程池中核心线程数最大值。
线程池中有两类线程,核心线程和非核心线程。核心线程默认情况下会一直存在于线程池中,即使这个核心线程什么都不干(铁饭碗),而非核心线程如果长时间的闲置,就会被销毁(临时工)。
7.2、maximumPoolSize:该线程池中线程总数最大值。
该值等于核心线程数量(corePoolSize) + 非核心线程数量。
7.3、keepAliveTime:非核心线程闲置超时时长。
非核心线程如果处于闲置状态超过该值,就会被销毁。如果设置allowCoreThreadTimeOut(true)默认false,则会也作用于核心线程。
如果线程池设置了allowCoreThreadTimeout参数为true,那么当空闲线程超过keepaliveTime后直接停掉。(不会判断线程数是否大于corePoolSize)即:最终线程数会变为0。一般使用默认false即可。
7.4、TimeUnit:表示keepAliveTime的单位。
timeunit.days; //天
timeunit.hours; //小时
timeunit.minutes; //分钟
timeunit.seconds; //秒
timeunit.milliseconds; //毫秒
timeunit.microseconds; //微妙
timeunit.nanoseconds; //纳
当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉,所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
7.5、BlockingQueue
常用的几个阻塞队列:
1、LinkedBlockingQueue:链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE,也可以指定大小。(无界队列)
2、ArrayBlockingQueue:数组阻塞队列,底层数据结构是数组,需要指定队列的大小。(有界队列)
3、SynchronousQueue:同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。(有界队列)
4、DelayQueue:延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。
注意:keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。ArrayBlockingQueue和DelayQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。
7.6、ThreadFactory:创建线程的工厂。
创建线程的工厂,用于批量创建线程,统一在创建线程时设置一些参数,如是否守护线程、线程的优先级等。如果不指定,会新建一个默认的线程工厂。
默认工厂创建的线程:同属于相同的线程组,具有同为 Thread.NORM_PRIORITY 的优先级,以及名为 “pool-XXX-thread-” 的线程名(XXX为创建线程时顺序序号),且创建的线程都是非守护进程。
例如:pool-5-thread-6。
7.7、RejectedExecutionHandler:拒绝处理策略。
线程数量大于最大线程数(maximumPoolSize)就会采用拒绝处理策略,四种拒绝处理的策略为:
1、ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException异常。(默认)
2、ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常。
3、ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序(如果再次失败,重复此过程)。
4、ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。
备注:最科学的的还是 AbortPolicy 提供的处理方式:抛出异常,由开发人员进行处理。
更多优秀文章,请扫码关注个人微信公众号或搜索“程序猿小杨”添加。
推荐文章:
1、SpringBoot使用@Async实现多线程异步;
2、线程池的主要处理流程及常用方法