java线程池的深入解析

本文围绕Java线程池展开,介绍了创建线程池的方法,不推荐用Executors类,推荐使用ThreadPoolExecutor并解释其构造参数。阐述了线程池相关类结构,列举了线程池种类及禁止用Executors创建的原因。还说明了线程池满时的拒绝策略,以及使用线程池的好处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程池

如何创建线程池

JDK中提供了创建线程池的类,大家首先想到的一定是Executors类,没错,可以通过Executors类来创建线程池,但是不推荐(原因后面会分析)。

在面试过程中经常会被问到请说说如何创建线程池,线程池的参数有哪些等等。 以下我们都会一一解答

Executors类只是个静态工厂,提供创建线程池的几个静态方法(内部屏蔽了线程池参数配置细节),而真正的线程池类是ThreadPoolExecutor。ThreadPoolExecutor构造方法如下:
在这里插入图片描述
参数解释

  • corePoolSize:核心线程数。如果等于0,则任务执行完后,没有任务请求进入时销毁线程池中的线程。如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。设置过大会浪费系统资源,设置过小导致线程频繁创建。

  • maximumPoolSize:最大线程数。必须大于等于1,且大于等于corePoolSize。如果与corePoolSize相等,则线程池大小固定。如果大于corePoolSize,则最多创建maximumPoolSize个线程执行任务

  • keepAliveTime:线程空闲时间。线程池中线程空闲时间达到keepAliveTime值时,线程会被销毁,只到剩下corePoolSize个线程为止。默认情况下,线程池的最大线程数大于corePoolSize时,keepAliveTime才会起作用。如果allowCoreThreadTimeOut被设置为true,即使线程池的最大线程数等于corePoolSize,keepAliveTime也会起作用(回收超时的核心线程)。

  • unit:TimeUnit表示时间单位。

  • workQueue:缓存队列。当请求线程数大于corePoolSize时,线程进入BlockingQueue阻塞队列。

  • threadFactory:线程工厂。用来生产一组相同任务的线程。主要用于设置生成的线程名词前缀、是否为守护线程以及优先级等。设置有意义的名称前缀有利于在进行虚拟机分析时,知道线程是由哪个线程工厂创建的。

  • handler:执行拒绝策略对象。当达到任务缓存上限时(即超过workQueue参数能存储的任务数),执行拒接策略,可以看作简单的限流保护。

以上解释可能会看不懂,没关系我们举一个列子画图来解释以上参数:
我们来举一个列子,银行,我们去银行办理业务,一个人比作一个线程,银行比作是线程池,银行的一个个受理窗口比作是线程池的线程数,序号比作是线程池的第几个参数。
在这里插入图片描述

平时比如周1-周5人比较少 银行上班的人员就比较少 所以银行一般只有2个受理窗口 最多只有5个受理窗口 所以比作线程池的最大线程数就是5,然后核心线程数就是2,所以345窗口是默认关闭的,只有2个窗口打开,这个时候我们人(线程)进入银行(线程池)办理业务 ,进去之后看见受理窗口没有满就直接去受理窗口(核心线程数)。

如下图:
在这里插入图片描述
这个时候核心线程数满了,如果门口还有人(线程)进来,会进入候客区(阻塞队列),如下图:

在这里插入图片描述
如果这个时候是人来的比较多(业务高峰期,线程数比较多) ,这个时候阻塞队列也满了。
在这里插入图片描述
如果再进人的话候客区也没有座位(阻塞队列已经满了放不下了),这个时候会顾客会投诉了,明明有这么多受理窗口却没开启窗口(线程池的线程数),这个时候经理会让一些工作人员回来上班,受理窗口会慢慢打开,如果核心线程数满了 阻塞队列也满了,再进线程的话,如果最大线程数没满,那么会打开线程数的窗口。线程池采用什么方式创建窗口那么就是第6个参数threadFactory
在这里插入图片描述
之后窗口慢慢打开打开到最大线程数就不能再打开了,这个时候最大受理窗口(最大线程数)满了,候客区(阻塞队列也满了),这个时候不能再进来人了,再进来人也处理不了这么多人的业务了。如图
在这里插入图片描述
这个时候银行(线程池)已经达到饱和 ,再进人银行就要挤满了,这个时候如果还是有人要进来就会出问题了,这个时候就要采取我们线程池第7个参数了,拒绝策略
在这里插入图片描述
具体的拒绝策略后续会讲到。
这个时候高峰期过去了,线程的人数也即将减少。如图
在这里插入图片描述
这个时候临时加班的那3个工作人员眼看也没有人来了,还在加班,这个时候经理就会说,如果你们这个窗口在5分钟之内的窗口都没有人来受理,那么你们就可以下班回家了。这个时候就是我们线程池的第3个和第4个参数,keepAliveTime和unit:TimeUnit,如果我们线程池除了核心线程数,也就是除了正常工作人员(前2个窗口),只要扩充的窗口在多少分钟之内没有人来受理就会关闭窗口。参数3,线程空闲时间,参数4,线程空间时间的单位
在这里插入图片描述

线程池相关类结构

ExecutorService接口继承了Executor接口,定义了管理线程任务的方法。

ExecutorService的抽象类AbstractExecutorService提供了submit、invokeAll()等部分方法实现,但是核心方法Executor.execute()并没有实现。

因为所有任务都在这个方法里执行,不同的线程池实现策略会有不同,所以交由具体的线程池来实现。

线程池种类

  • newSingleThreadExecutor:创建单线程的线程池,核心线程数和最大线程数都为1,相当于串行执行。
    在这里插入图片描述

  • newFixedThreadPool:创建固定线程数的线程池。核心线程数等于最大线程数,不存在空闲线程,keepAliveTime为0。
    在这里插入图片描述

  • newCachedThreadPool:核心线程数为0,最大线程数为Integer.MAX_VALUE,是一个高度可伸缩的线程池。存在OOM风险。keepAliveTime为60,工作线程处于空闲状态超过keepAliveTime会回收线程。
    在这里插入图片描述

禁止直接使用Executors创建线程池原因:
Executors.newCachedThreadPool和Executors.newScheduledThreadPool两个方法最大线程数为Integer.MAX_VALUE,最大线程数可以达到21亿多,创建这么多线程,肯定会抛出OOM异常。

Executors.newSingleThreadExecutor和Executors.newFixedThreadPool两个方法的workQueue参数为new LinkedBlockingQueue(),容量为Integer.MAX_VALUE,阻塞队列的容量最大是21亿,会堆积大量的请求,如果瞬间请求非常大,会有OOM风险。

如果线程池满了怎么办
会执行线程拒绝策略

ThreadPoolExecutor提供了四个公开的内部静态类:

  • AbortPolicy:默认,丢弃任务并抛出RejectedExecutionException异常。

  • DiscardPolicy:丢弃任务,但是不抛出异常(不推荐)。

  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中。

  • CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行。

友好的拒绝策略:

保存到数据库进行削峰填谷。在空闲时再提出来执行。
转向某个提示页面
打印日志
在这里插入图片描述

为什么要用线程池?
池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。

这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值