68.什么是线程池?为什么要用线程池?
线程池 是一个装有很多线程的池子,这些线程可以用来处理各种任务。当有任务需要执行时,线程池会提供一个线程来执行任务,任务完成后线程不会被销毁,而是回到池子里等待下一个任务。
为什么要用线程池?
-
节省资源:
- 线程池通过复用已有的线程,减少了创建和销毁线程的次数,节省了资源。
-
响应更快:
- 有了线程池,当有任务需要执行时,可以立即从池子里拿一个线程来用,而不需要现创建一个新的,任务可以更快地开始执行。
-
更好地管理线程:
- 如果不限制线程的数量,系统可能会因为创建太多线程而变得不稳定。线程池可以控制线程的数量,确保系统稳定运行。
-
corePoolSize(核心线程池的大小):
- 核心线程池的大小指的是线程池中保持活动的最小线程数,即使这些线程处于空闲状态也不会被销毁。当需要执行任务时,如果当前活动线程数少于核心线程数,线程池会创建新线程来执行任务。
-
maximumPoolSize(线程池能创建线程的最大个数):
- 最大线程池大小指的是线程池中能够同时容纳的最大线程数。当线程数达到最大值时,如果有新的任务提交,且任务队列已满,则会触发拒绝策略。
-
keepAliveTime(空闲线程存活时间):
- 这是指当线程池中的线程数超过核心线程数时,多余的空闲线程在终止之前能保持多长时间的存活。如果超过这个时间限制,多余的线程会被终止。
-
unit(时间单位,为 keepAliveTime 指定时间单位):
- 这是指 keepAliveTime 的时间单位,可以是秒、毫秒、微秒或纳秒等。
-
workQueue(阻塞队列,用于保存任务的阻塞队列):
- 任务队列用于保存等待执行的任务。当核心线程池的线程都在忙时,新任务会被放入这个队列中等待执行。常用的阻塞队列有
LinkedBlockingQueue
、ArrayBlockingQueue
等。
- 任务队列用于保存等待执行的任务。当核心线程池的线程都在忙时,新任务会被放入这个队列中等待执行。常用的阻塞队列有
-
threadFactory(创建线程的工厂类):
- 线程工厂用于创建新线程。通过自定义线程工厂,可以设置线程的名称、优先级等属性。默认情况下,使用
Executors.defaultThreadFactory()
创建线程。
- 线程工厂用于创建新线程。通过自定义线程工厂,可以设置线程的名称、优先级等属性。默认情况下,使用
-
handler(饱和策略,即拒绝策略):
- 当线程池达到最大线程数且任务队列已满时,新的任务会被拒绝执行。这时候可以通过拒绝策略来处理被拒绝的任务。常用的拒绝策略有
AbortPolicy
(直接抛出异常)、CallerRunsPolicy
(由调用者线程执行任务)、DiscardPolicy
(直接丢弃任务)、DiscardOldestPolicy
(丢弃队列中最老的任务)。
- 当线程池达到最大线程数且任务队列已满时,新的任务会被拒绝执行。这时候可以通过拒绝策略来处理被拒绝的任务。常用的拒绝策略有
70.线程池的种类、区别和应用场景
-
newCachedThreadPool
- 简介:
newCachedThreadPool
创建一个可缓存线程池。如果当前线程池中的线程数量超过处理需求,它会灵活地回收空闲线程;当需要增加时,它也可以灵活地添加新线程,而不受线程池长度的限制。 - 使用场景: 适用于执行大量短期异步小任务的场景,例如短时间内需要处理大量请求。
- 简介:
-
newFixedThreadPool
- 简介:
newFixedThreadPool
创建一个固定大小的线程池。线程池的大小由参数决定,超出的任务会在队列中等待。固定线程池的大小最好根据系统资源进行设置。 - 使用场景: 适用于执行长期任务或需要稳定线程数量的场景,例如服务器应用程序中需要处理稳定数量的并发请求。
- 简介:
-
newScheduledThreadPool
- 简介:
newScheduledThreadPool
创建一个支持定时及周期性任务执行的线程池。可以设定任务在指定时间后执行或周期性地执行。 - 使用场景: 适用于需要周期性执行任务的场景,例如定时备份、定时报告生成等。
- 简介:
-
newSingleThreadExecutor
- 简介:
newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个工作线程,如果线程因为异常结束,会有一个新线程来替代它,以保证任务的顺序执行。 - 使用场景: 适用于需要顺序执行任务的场景,例如需要按顺序处理的日志文件写入操作。
- 简介:
71.线程池的拒绝策略有哪些
在Java中,当线程池无法接受新的任务时,会采取一些拒绝策略来处理。常见的拒绝策略有以下几种:
-
AbortPolicy (默认)
- 简介: 这是线程池的默认拒绝策略。当线程池队列已满且无法处理新的任务时,
AbortPolicy
会抛出RejectedExecutionException
异常,通知调用者任务无法被执行。 - 使用场景: 适用于需要明确知道任务被拒绝,并进行相应异常处理的场景。
- 简介: 这是线程池的默认拒绝策略。当线程池队列已满且无法处理新的任务时,
-
DiscardPolicy
- 简介: 该策略是
AbortPolicy
的静默版本。如果线程池队列已满,新任务会被直接丢弃,并且不会有任何异常抛出。 - 使用场景: 适用于不需要处理任务被拒绝情况,且能够容忍任务丢失的场景。
- 简介: 该策略是
-
DiscardOldestPolicy
- 简介: 该策略会丢弃线程池队列中最老的任务(队首任务),然后尝试重新提交新任务到线程池。
- 使用场景: 适用于希望保证新任务能够被执行,且能够容忍旧任务被丢弃的场景。
-
CallerRunsPolicy
- 简介: 如果线程池无法接受新任务,该策略会由调用线程来执行该任务。也就是说,任务会在提交任务的线程中运行,而不是在线程池中运行。
- 使用场景: 适用于需要确保任务不被丢弃,并且可以接受调用线程执行任务的场景。例如,降低提交新任务的速率,以减轻线程池的负载。
72.在 Java 中 Executor 和 Executors 的区别
(好的,我们来逐句理解这幅图里的话:
-
"Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。"
- 这句话的意思是,
Executors
是一个帮助类,它提供了创建线程池的方法。根据不同的需求,我们可以使用Executors
提供的不同方法来创建不同类型的线程池,以满足我们的业务需求。例如:Executors.newFixedThreadPool(int nThreads)
:创建一个固定大小的线程池,线程池中的线程数量是固定的。Executors.newCachedThreadPool()
:创建一个可缓存的线程池,线程池中的线程数量可以根据需要动态调整。Executors.newSingleThreadExecutor()
:创建一个单线程的线程池,这个线程池只有一个线程来执行任务。
- 这句话的意思是,
-
"Executor 接口对象能执行我们的线程任务。"
- 这句话的意思是,
Executor
是一个接口,它定义了一个执行线程任务的方法。任何实现了Executor
接口的类都可以用来执行线程任务。Executor
接口的核心方法是execute(Runnable command)
,通过这个方法,我们可以将线程任务提交给Executor
对象执行。
- 这句话的意思是,
-
"ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法让我们能获得任务执行的状态并且可以获取任务的返回值。"
- 这句话的意思是,
ExecutorService
接口是Executor
接口的子接口,它不仅包含Executor
接口的方法,还扩展了更多的方法。ExecutorService
提供了一些附加功能,比如:- 提交任务并获取任务的执行结果(
submit(Callable<T> task)
和submit(Runnable task, T result)
方法)。 - 管理线程池的生命周期(
shutdown()
和shutdownNow()
方法)。 - 获取任务的执行状态(返回
Future
对象,Future
对象可以用来检查任务是否完成、获取任务的返回值、取消任务等)。
- 提交任务并获取任务的执行结果(
- 这句话的意思是,
总结一下,这幅图解释了 Java 中 Executors
工具类、Executor
接口和 ExecutorService
接口的作用和关系。Executors
工具类提供了创建不同类型线程池的方法,Executor
接口定义了执行线程任务的方法,而 ExecutorService
接口在 Executor
接口的基础上进行了扩展,提供了更多的功能来管理和获取任务的执行状态和结果。)
73.线程池有哪些状态
74 线程池中 submit() 和 execute() 方法有什么区别?
-
相同点:
- 都可以开启线程执行池中的任务。
-
不同点:
- 接受参数:
execute()
只能执行Runnable
类型的任务。submit()
可以执行Runnable
和Callable
类型的任务。
- 返回值:
submit()
方法会返回一个Future
对象,持有计算结果。execute()
没有返回值。
- 异常处理:
submit()
方法便于处理异常,返回的Future
对象可以捕获异常。execute()
方法需要自己处理异常。
- 接受参数:
75.分析线程池的实现原理和线程的调度过程
提交一个任务到线程池中,线程池的处理流程如下:
-
核心线程数量未满
- 如果当前运行的线程数小于核心线程数,线程池会创建一个新线程来执行任务。
-
核心线程数已满,任务队列未满
- 如果当前运行的线程数等于或大于核心线程数,但小于最大线程数,且任务队列未满,任务会被放入任务队列中等待执行。
-
核心线程数已满,任务队列已满
- 如果任务队列已满,但当前运行的线程数小于最大线程数,线程池会创建一个新线程来执行任务。
-
最大线程数已满
- 如果当前运行的线程数已经等于最大线程数,且任务队列也已满,新提交的任务会被拒绝,并调用拒绝策略进行处理。
76.线程池的最大线程数目根据什么确定
线程池的最大线程数取决于任务类型:
-
CPU密集型任务:
- 最大线程数设置为 CPU 核心数 + 1。这样可以确保每个线程都有任务执行。
-
IO密集型任务:
- 最大线程数设置为 2 * CPU 核心数。因为 IO 操作会阻塞线程,多增加线程数能提高效率。
77.线程池如何调优
-
CPU密集型任务
- 配置尽可能少的线程数,通常为 CPU 核心数 + 1。
-
IO密集型任务
- 配置较多的线程数,因为线程常常处于等待状态,通常为 2 * CPU 核心数。
-
混合型任务
- 尽量将任务拆分为 CPU 密集型和 IO 密集型任务。如果任务执行时间相差不大,可以并发执行;如果相差较大,则需分别处理。
-
任务优先级
- 使用优先级队列(如
PriorityBlockingQueue
)处理优先级不同的任务,让优先级高的任务先执行。
- 使用优先级队列(如
-
执行时间不同的任务
- 交给不同规模的线程池处理,或使用优先级队列,确保短时间任务优先执行。
-
依赖数据库的任务
- 由于需等待数据库响应,线程数应设置得较少,以更好利用 CPU。
-
使用有界队列
- 增加系统稳定性和预警能力。根据需求设置队列大小,如几百至几千。避免使用无界队列,以免内存溢出导致系统不可用。
78.线程池如何实现动态修改?
-
设置线程池参数
-
线程池提供了一些
setter
方法,可以动态调整以下参数:- 核心线程数
- 最大线程数
- 空闲线程存活时间
- 拒绝策略
-
可以将线程池配置参数放入配置中心,当需要调整时,在配置中心修改即可。
-
-
什么时候修改?
- 监控和报警: 监控线程池状态指标,当指标异常时进行报警。
- 分析和调整: 分析指标异常原因,评估处理策略,通过线程池提供的接口进行动态修改。
79.使用无界队列的线程池会导致什么问题?
例如 newFixedThreadPool
使用了无界的阻塞队列 LinkedBlockingQueue
,如果线程池获取到的任务执行时间较长,会导致队列中的任务不断积压,内存使用持续增加,最终可能导致内存溢出(OOM)。。
。