Java 线程池概念总结(thread pool)

一、池化思想的应用

池化思想是一种常见软件设计和优化技术。以下是几个常见池化思想应用场景:

  1. 线程池:线程池是池化思想一个典型应用。通过预先创建一组线程并将它们置于就绪状态,以复用线程减少线程创建和销毁的开销,并提高系统性能和响应速度。

  2. 连接池:连接池是数据库和网络编程中常见应用。通过提前创建一批数据库连接或网络连接并将它们置于可复用状态以避免频繁创建释放连接,从而提高数据库访问和网络通信效率。

  3. 对象池:对象池用于管理和重复使用可复用的对象。例如,数据库连接池、线程安全的缓存池等。通过对象池,可以避免频繁创建和销毁对象,提高系统的性能和资源利用率。

  4. 内存池:频繁内存分配和释放可能会导致内存碎片,降低内存利用效率。内存池将一块连续内存空间划分为多个固定大小的块并维护一个空闲块列表。程序可以从内存池中申请和释放内存块以减少内存碎片和提高内存的分配性能。

  5. 字符串池:字符串池通过将相同的字符串共享在一个池中,减少内存占用和提高字符串比较的速度。Java中字符串常量池就是一种典型字符串池应用。

        这些池化思想的应用都旨在提高系统性能、资源利用率和开发效率,避免重复创建和销毁对象,从而降低系统开销,并提供更好的用户体验。

二、线程池的工作原理

线程池的工作原理可以基本分为以下几个步骤:

  1. 初始化线程池:创建线程池管理器,并设置线程池参数,包括线程数量、工作队列大小等。

  2. 创建线程:根据线程池初始配置,创建指定数量线程并将它们置于就绪状态等待执行任务。

  3. 接收任务:当有任务需要执行时线程池会接收并入队等待处理。任务可以是实现Runnable接口或Callable接口的对象。

  4. 任务调度:线程池中的线程从工作队列中获取任务,按照预设的调度策略进行选择,如先进先出(FIFO)、最近最少使用(LRU)等。

  5. 执行任务:选中的线程会从工作队列中取出任务,并执行任务的run()或call()方法。任务执行完毕后,线程进入空闲状态,等待下一个任务。

  6. 线程回收:当线程池处于空闲状态一段时间后,管理器可能会决定销毁部分线程,以节省系统资源。线程销毁后,线程池的线程数量会相应减少。

  7. 错误处理:线程池会捕获任务执行过程中产生的异常,防止线程因未捕获的异常而崩溃。异常处理策略可以根据具体需求进行自定义,如重新执行、记录日志、忽略等。

        通过上述工作原理,线程池实现了线程的复用和资源的合理利用,使得系统能够更好地处理并发任务,提高性能和响应速度。同时,线程池还能控制线程数量,避免过多的线程造成资源消耗和性能下降,提供了对线程的管理机制。

三、简述一下你对线程池的理解

       如果问到了这样的问题,可以展开说一下线程池如何用、线程池的好处、线程池的启动策略合理利用线程池能够带来三个好处。
        第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
        第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
        第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

        线程池是一种用于管理和复用线程的机制。它由一个线程池管理器、工作队列和一组线程组成。线程池管理器负责创建、销毁和监控线程池中的线程。它根据系统负载和预设条件来动态地调整线程数量,保证线程池的高效利用和合理分配。工作队列用于存储待处理的任务。线程池中的线程从工作队列中取出任务并执行。当任务数量超过线程池的处理能力时,任务会被暂时放入队列中,待有空闲线程时再进行处理。

线程是不是越多越好?

  1. 线程在java中是一个对象,更是操作系统的资源,线程创建、销毁需要时间。如果创建时间+小会时间>执行任务时间就很不合算。
  2. java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈大小1M,这个栈空间是需要从系统内存中分配的。线程过多,会消耗很多的内存。
  3. 操作系统需要频繁切换线程上下文(每个线都想被运行),影响性能。

线程池的推出,就是为了方便边的控制线程数量。

        使用线程池时,需要考虑线程数量、工作队列大小以及任务调度策略等因素,以确保线程池能够按预期工作并提供最佳的性能。同时,我们还要注意处理异常情况,避免线程由于未捕获的异常而导致整个线程池崩溃。

为什么不建议使用 Executors静态工厂构建线程池?

        阿里巴巴Java开发手册,明确指出不允许使用Executors静态工厂构建线程池,原因如下:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险,说明:Executors返回的线程池对象的弊端如下:

1:FixedThreadPool 和 SingleThreadPool:
允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
2:CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

        创建线程池的正确姿势:避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

private static ExecutorService executor=newThreadPoolExecutor(10,10,60L,TimeUnit.SECONDS,new ArrayBlockingQueue(10));

或者是使用开源类库:开源类库,如apache和guava等。

线程池如何知道一个线程任务已经执行完成?

        线程池通常使用一种称为"Future"的机制来跟踪线程任务的执行状况。

        在Java中,可以通过java.util.concurrent.Future接口来表示异步计算的结果。线程池提交的任务可以返回一个Future对象,在任务执行完成后,可以通过该对象获取任务的执行结果或判断任务是否完成。

具体而言,可以按照以下步骤使用Future来知道线程任务是否已经执行完成:

  1. 提交任务:将要执行的任务提交给线程池,并获得返回的Future对象。

  2. 判断任务状态:通过调用Future的isDone()方法判断任务是否已经完成。如果任务完成,返回true;否则返回false。

  3. 获取任务结果:如果任务已经完成,可以通过调用Future的get()方法获取任务的执行结果。该方法会阻塞当前线程,直到任务完成并返回结果。

        需要注意的是,如果任务还未完成,调用get()方法会使当前线程阻塞,直到任务执行完成。如果不希望阻塞当前线程,可以使用isDone()方法轮询判断任务状态,或者使用带有超时参数的get()方法来等待一段时间后获取结果。

        总之,通过使用Future对象,线程池可以方便地跟踪和获取线程任务的执行状态和结果,从而实现对任务的管理和控制。

参考:

Java 中线程池的 7 种创建方式! - 磊哥|www.javacn.site - 博客园 (cnblogs.com)

Java线程池实现原理及其在美团业务中的实践 - 美团技术团队 (meituan.com)

JUC线程池: ThreadPoolExecutor详解 | Java 全栈知识体系 (pdai.tech)

线程池面试题 - 小魚人 - 博客园 (cnblogs.com)

猜你喜欢

转载自blog.csdn.net/weixin_49171365/article/details/129570630