Java中的线程池如何实现,一文彻底搞懂

本文详细介绍了Java线程池的工作原理、核心参数、线程池生命周期和拒绝策略,包括ThreadPoolExecutor和ScheduledThreadPoolExecutor的实现。通过分析构造参数、线程数、存活时间和任务队列,阐述了线程池如何降低系统性能开销和防止资源浪费。此外,还讨论了线程工厂、线程组和拒绝策略的选择,以及Executors提供的线程池类型,如FixedThreadPool、CachedThreadPool和ScheduledThreadPool的特点与应用场景。
摘要由CSDN通过智能技术生成

前言

为什么要用线程池一键获取线程相关资料,还可获取最新java面试真题库

在 HotSpot VM 的线程模型中,Java 线程被一对一映射为内核线程。

Java 在使用线程执行程序时,需要调用操作系统内核的 API,创建一个内核线程,操作系统要为线程分配一系列的资源;当该 Java
线程被终止时,这个内核线程也会被回收。因此 Java 线程的创建与销毁的成本很高,从而增加系统的性能开销。

除此之外,无限制地创建线同样会给系统带来性能问题。因为 CPU
核数是有限的,大量的线程上下文切换会增加系统的性能开销。同时无限制地创建线程还可能导致 OOM。

为了解决上述两类问题,于是引入了线程池概念。

对于第一类问题,频繁创建与销毁线程:线程池复用线程,提高线程利用率,避免频繁的创建与销毁线程。

对于第二类问题,大量创建线程:线程池限制线程创建的最大数量,防止无限制地创建线程。

线程池提供了一种方式来管理线程和消费,维护基本数据统计等工作,比如统计已完成的任务数;

介绍线程池框架 Executor

Java 提供了一套线程池框架 Executor。

这个框架包括了 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 两个核心线程池。

  • ThreadPoolExecutor 是用来执行被提交的任务
  • ScheduledThreadPoolExecutor 是用来执行定时任务。

还有一个 ForkJoinPool 则是为 ForkJoinTask 定制的线程池,与通常意义的线程池有所不同。

除此之外, Executors 类为我们提供了各种方便的静态工厂方法来简化线程池的创建。

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
    }

从类的定义我们可以看到,ScheduledThreadPoolExecutor 类继承自 ThreadPoolExecutor 类,因此下面我们就重点看看 ThreadPoolExecutor 类是如何实现线程池的。

ThreadPoolExecutor 的「构造参数」和「工作行为」

ThreadPoolExecutor 的构造函数非常复杂,最完备的构造函数有 7 个参数,如下面代码所示。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

下面我们一一介绍这些参数的意义。(参考了 ThreadPoolExecutor 的 javadoc )

线程数

corePoolSize:核心线程数

maximumPoolSize:最大线程数

线程池会根据 corePoolSize 和 maximumPoolSize 这两个参数的值,自动调整线程池大小。

当我们向线程池中提交任务时:

  • 如果当前有少于 corePoolSize
    个线程正在运行,那么将创建一个新的线程来处理请求,即使其他工作线程处于空闲状态(也就是说,前面说的正在运行的线程是指,所有已经创建的线程,包括处于空闲状态的线程)
  • 如果当前有大于等于 corePoolSize 个线程正在运行,则尝试把任务加到任务队列中
  • 如果任务队列未满,则加入成功,排队等待线程处理
  • 如果任务队列已满,并且当前有不超过 maximumPoolSize 个线程,则创建一个新的线程来处理请求
  • 如果当前有 maximumPoolSize 个线程正在运行,并且任务队列已满,那么线程池会拒绝接收任务,并按照指定的拒绝策略处理任务

通过将 corePoolSize 和 maximumPoolSize 设置为相同的值,我们可以创建固定大小的线程池。

通过将 maximumPoolSize 设置为一个本质上无界的值,例如 Integer.MAX VALUE,允许线程池容纳任意数量的线程。

通常,corePoolSize 和 maximumPoolSize 这两个参数的值只在构造 ThreadPoolExecutor 时设置,但这两个参数的值也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 动态修改。通过动态修改参数的值,我们可以做到动态配置自定义线程池,感兴趣的可以了解一下。

在创建完线程池之后,默认情况下,线程池中没有任何线程,只有在新任务到达时线程才会被创建(new)和执行(start),但这可以通过使用 prestartCoreThread() 或 prestartAllCoreThreads() 方法来动态覆盖。如果使用非空队列构造线程池,则可能需要预启动线程。预启动线程在抢购系统中也经常被用到。

非核心线程的存活时间

keepAliveTime: 线程存活的实现

TimeUnit: 存活时间的单位(小时、分钟、秒、毫秒)

如果线程池当前有超过 corePoolSize 个线程,并且线程空闲的时间超过了 keepAliveTime,那么这些线程将被销毁,这样可以避免线程没有被使用时的资源浪费。

这个参数也可以使用方法 setKeepAliveTime(long,TimeUnit) 动态修改。

默认情况下,只有存在多于 corePoolSize 个线程时,才会应用 keep-alive 策略;但通过 allowCoreThreadTimeOut(boolean) 方法,将参数 allowCoreThreadTimeOut 的值设置为 true,则 keep-alive 策略也可应用于不超过 corePoolSize 个线程时。

任务队列

BlockingQueue:任务队列,用来储存等待被执行的任务

  • 如果线程池当前有大于等于 corePoolSize 个线程正在运行,则尝试把任务加到任务队列中
  • 如果任务队列未满,则加入成功,排队等待线程处理
  • 如果任务队列已满,并且当前有不超过 maximumPoolSize 个线程,则创建一个新的线程来处理请求

也就是说,当线程数量达到 corePoolSize 个之后,不会立即扩容线程池,而是先把任务堆积到任务队列中,任务队列满了之后,才考虑扩容线程池,一直到线程个数达到 maximumPoolSize 为止。

这个任务队列必须必须是 BlockingQueue 类型的,也就是必须是阻塞队列。

阻塞队列其实就是在队列基础上支持了阻塞操作。

简单来说,阻塞操作就是:

  • 如果队列为空,那么从队头取数据的操作会被阻塞,直到队列中有数据才能返回;
  • 如果队列已满,那么从队尾插入数据的操作会被阻塞,直到队列中有空闲位置并插入数据后,才能返回。

Java 中 BlockingQueue 类型的队列也有很多,比如:(共 8 个)

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列

  • LinkedBlockingQueue:基于链表结构的阻塞队列。可以在创建队列时指定容量;如果没有指定容量,那么其容量限制就自动被设置为
    Integer.MAX_VALUE,成为了无界队列。

  • SynchronousQueue:不存储元素的阻塞队列。每个移除操作必须等待另一个线程的插入操作,反之每个插入操作也都要等待另一个线程的移除操作。这个队列的容量是
    0。

  • PriorityBlockingQueue:具有优先级的无界阻塞队列

  • DelayQueue:支持延时获取的无界阻塞队列,内部用 PriorityQueue 实现

  • LinkedTransferQueue:基于链表结构的无界阻塞队列

  • LinkedBlockingDeque:基于双向链表的阻塞队列,可以在创建队列时指定容量

  • DelayedWorkQueue:无界阻塞队列

总结来说,BlockingQueue 类型的队列可以从以下两个维度划分:

  • 底层结构:数组 or 单向链表 or 双向链表 or 优先级队列(DelayQueue 基于 PriorityQueue)

  • 是否有界:有界 or 无界 or 既可以有界又可以无界

理论上两个维度中两两组合,就可以构成一种类型的 BlockingQueue。
在这里插入图片描述

线程工厂

  • ThreadFactory:线程工厂,用来创建线程
Python面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它将数据和操作封装在对象,通过对象之间的交互实现程序的设计和开发。下面是一些关键概念,帮助你更好地理解Python面向对象编程。 1. (Class):是对象的蓝图或模板,描述了对象的属性和行为。它定义了对象的特征和方法。例如,我们可以定义一个名为"Car"的来表示汽车,其包含属性(如颜色、型号)和方法(如加速、刹车)。 2. 对象(Object):对象是的实例,是具体的实体。通过实例化,我们可以创建一个对象。例如,我们可以创建一个名为"my_car"的对象,它是基于"Car"的实例。 3. 属性(Attribute):属性是对象的特征,用于描述对象的状态。每个对象都可以具有一组属性。例如,"Car"的属性可以包括颜色、型号等。 4. 方法(Method):方法是对象的行为,用于定义对象的操作。每个对象都可以具有一组方法。例如,"Car"的方法可以包括加速、刹车等。 5. 继承(Inheritance):继承是一种机制,允许我们创建一个新(称为子),从现有(称为父)继承属性和方法。子可以扩展或修改父的功能。继承可以实现代码重用和层次化设计。 6. 多态(Polymorphism):多态是一种特性,允许不同的对象对同一方法做出不同的响应。多态提高了代码的灵活性和可扩展性。 7. 封装(Encapsulation):封装是一种将数据和操作封装在对象的机制,隐藏了对象的内部实现细节,只暴露必要的接口给外部使用。这样可以保护数据的安全性,提供了更好的模块化和代码复用性。 通过理解这些概念,你可以更好地掌握Python面向对象编程。在实践,你可以使用来创建对象,操作对象的属性和调用对象的方法,通过继承和多态实现代码的灵活性和可扩展性,通过封装保护数据的安全性和提高代码的可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值