文章目录
- 1. java中的线程池是如何实现的
- 2. 创建线程池的几个核心参数
- 3. Java 中线程池的执行流程
- 4. 为什么要使用线程池
- 5. 线程池的拒绝策略
1. java中的线程池是如何实现的
Java 中的线程池是通过 ThreadPoolExecutor 类实现的。ThreadPoolExecutor 继承自 AbstractExecutorService,并实现了 Executor、ExecutorService 和 Future 接口,它可以为程序提供管理和控制多个工作者线程的功能,提高线程重用性并避免线程频繁创建销毁的开销。
ThreadPoolExecutor 的主要组成部分包括:
corePoolSize:核心线程池大小,默认情况下核心线程不会被回收,即使没有任务需要执行。
maximumPoolSize:线程池最大线程数,用于控制线程数上限,当队列已满且当前线程数小于最大线程数时,线程池会新建更多线程来执行就绪任务。
workQueue:工作队列,用于存放等待执行的任务。ThreadPoolExecutor 提供了四种类型的工作队列:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue 和 PriorityBlockingQueue。
threadFactory:线程工厂,用于创建新的线程。
handler:拒绝策略,用于当工作队列已满、线程池中也达到最大线程数时,执行拒绝策略,通常有四种预定义的拒绝策略,AbortPolicy、DiscardPolicy、DiscardOldestPolicy 和 CallerRunsPolicy。
在使用线程池时,一般需要通过 Executors 工具类提供的一些静态方法创建 ThreadPoolExecutor 对象,并设置相应的参数来达到需要的执行效果。线程池中会通过 allocateThread 等方法来实现核心线程和工作线程的访问,通过 submit 和 execute 方法来接收新的任务并执行。
Java 中的线程池是通过 ThreadPoolExecutor 类实现的,它内部维护了一定数量的线程,能为程序提供线程管理和控制功能,避免频繁创建销毁线程的开销,提高线程重用性并提高整体性能。同时,开发者可以通过设置不同的参数来满足特定场景的需求。
2. 创建线程池的几个核心参数
创建线程池时,需要设置以下几个核心参数:
corePoolSize:核心线程池大小,用于控制线程数量的基础值。当一个新任务被提交到线程池,如果当前线程数小于 corePoolSize,那么线程池会为该任务分配一个新线程来处理它;如果线程数已经达到或超过了 corePoolSize,那么任务会被加入等待队列中等待处理。
maximumPoolSize:线程池最大线程数,用于控制线程数上限。当等待队列已满且当前线程数小于 maximumPoolSize 时,线程池会创建更多的线程来执行就绪任务,最大可创建线程数为 maximumPoolSize - corePoolSize。
workQueue:工作队列,对任务进行排队等待处理。当任务提交给线程池时,会先将任务保存到工作队列中,然后由空闲线程从队列取出任务并执行。
keepAliveTime:线程活动保持时间,即当线程空闲时间达到该值时,线程池会判断是否要回收该线程。keepAliveTime 需要与 TimeUnit(时间单位)一起使用,例如 TimeUnit.SECONDS。
threadFactory:线程工厂,用于创建新线程。可以通过实现 ThreadFactory 接口自定义线程创建方式以及给线程起特定的名字等。
handler:拒绝策略,用于当工作队列已满、线程池中也达到最大线程数时,执行拒绝策略。一般情况下,有四种预定义的拒绝策略:AbortPolicy(默认),DiscardPolicy、DiscardOldestPolicy 和 CallerRunsPolicy。
以上这些参数要根据实际情况进行设置,例如任务类型、任务量大小、执行时长等都可能会影响这些参数的选择和调整。
3. Java 中线程池的执行流程
Java 中的线程池通常由 java.util.concurrent.Executor
和其子接口 java.util.concurrent.ExecutorService
提供。 Executor 主要负责维护工作队列和线程池。当程序向 Executor 提交任务时,它将选择一个可用的线程并委派该任务执行;如果所有线程均已被占用,则该任务将被放到队列中等待执行。
线程池的执行流程如下:
-
当线程池被创建时,它会预先创建指定数量的线程,这些线程处于空闲状态,并等待着任务的到来。
-
当任务被提交后,线程池会首先判断是否有空闲线程,如果有,则将任务分配给某个空闲线程去执行;如果没有,则将该任务添加至任务队列等待执行。
-
在某个线程开始执行一个任务时,线程池会将任务从任务队列中取出,并将该任务交给当前线程执行。
-
线程执行完毕后并不会立即结束,而是重新加入到线程池的线程池(可重用)中,等待下一个任务的分配。
-
当任务队列中有新任务时,线程池会根据先进先出的原则逐一从任务队列中获取任务,并将任务分配给其中一个空闲线程执行,重复步骤 3-4,直到任务队列为空。
-
当某个线程空闲的时间超过指定的最大存活时间(keepAliveTime)时,线程池会将该线程从线程池中移除,以减少系统资源占用。
-
线程池可由调用 ExecutorService 的 shutdown() 方法来关闭。当执行此操作时,线程池不再接受新任务,并且会依次将正在等待执行的任务和已经提交但还未被执行的任务继续执行完毕后停止。
线程池通过缓存线程以及统一调度和控制线程的创建、销毁等功能,尽可能地利用线程,避免了频繁创建和销毁线程带来的开销和性能问题,提高了系统的性能和稳定性。
4. 为什么要使用线程池
使用线程池的主要原因是在多线程编程时可以更有效地利用系统资源,提高程序并发性能。
以下是使用线程池的几个优点:
降低开销:创建和销毁线程是一项耗费资源的操作。使用线程池在应用程序中重用线程,避免了频繁创建和销毁线程所带来的开销,降低了系统负担,提升了执行效率和吞吐量。
提高响应速度:当有新任务到来时,线程池中的线程可以立即响应,执行任务,缩短了任务等待时间,提高了系统的响应速度。
降低资源占用:通过限制线程的数量、大小等设置,可以避免过度的资源占用问题,确保系统运行的稳定性和可靠性。
统一管理:线程池为整个应用程序提供了一个统一的线程管理,对于线程的调度、监控、重用和回收等方面进行有效的管理和控制,提升了系统的可维护性和可扩展性。
控制最大并发数目:通过合理设置线程池的最大线程数量,可以防止某些代码片段(例如网络连接)出现过多的并发访问并导致系统瘫痪。
使用线程池能够更好地利用系统资源、提高并发性能和可靠性,减小资源消耗和浪费,是多线程编程不可缺少的重要工具和技巧。
5. 线程池的拒绝策略
线程池中的任务队列有一个固定的容量,当该队列已被填满,并且所有线程均处于忙碌状态时,新提交的任务就需要通过拒绝策略进行处理。线程池默认提供了四种拒绝策略:
-
AbortPolicy(默认):该策略是线程池默认的拒绝策略。当向线程池提交任务失败时,直接抛出 RejectedExecutionException 异常并停止程序。
-
CallerRunsPolicy:当向线程池提交任务失败时,将此任务交给提交该任务的线程来执行。这样做会降低新任务的速度,但可以保证当前正在执行的任务小概率被堵塞。
-
DiscardOldestPolicy:当向线程池提交任务失败时,删除队列头部的任务,然后重新提交当前任务。
-
DiscardPolicy:当向线程池提交任务失败时,直接将该任务丢弃。
如果默认的四种拒绝策略无法满足业务需求或者对业务代码产生负面影响,开发人员也可以基于 ThreadPoolExecutor 的子类重写 RejectedExecutionHandler 接口自定义特定的拒绝策略,以便更好地满足实际项目的需要。