Java中自定义线程池

一、介绍

1. 什么是线程池

线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制:它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

在Java中,池化思想主要体现在资源管理上,以提高程序的性能和资源的利用效率。池化技术允许预先创建和维护一组可重用的对象,这些对象可以在需要时被快速分配,使用后归还给池,而不是每次需要时都创建新对象,这样可以显著减少对象创建和销毁的开销。

2.为什么要使用线程池

使用线程池的好处如下:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。可以直接从线程池中取出已经创建好的线程。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

二、如何创建线程池

线程池的创建方法总共有 7 种,但总体来说可分为 2 类:

  • 通过 ThreadPoolExecutor 创建的线程池。
  • 通过 Executors 创建的线程池。

线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):

  • Executors.newFixedThreadPool():创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  • Executors.newCachedThreadPool():创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收多余的线程,若线程数不够,则新建线程;
  • Executors.newSingleThreadExecutor():创建单个线程数的线程池,它可以保证先进先出的执行顺序;
  • Executors.newScheduledThreadPool():创建一个可以执行延迟任务的线程池;
  • Executors.newSingleThreadScheduledExecutor():创建一个单线程的可以执行延迟任务的线程池;
  • Executors.newWorkStealingPool():创建一个抢占式执行的线程池(任务执行顺序不确定)JDK 1.8 添加。
  • ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置。

虽然有这么多创建线程的方式,但是在《阿里巴巴 Java 开发手册》强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors的使用非常简单和直观,但是它有一些默认配置上的缺陷,特别是在处理高并发或者长时间运行的应用程序时,这些缺陷可能导致系统资源耗尽,甚至崩溃。

三、通过ThreadPoolExecutor 创建

在这里插入图片描述
ThreadPoolExecutor 最多可以设置 7 个参数:

  1. corePoolSize:核心线程数,线程池中始终存活的线程数。
  2. maximumPoolSize:最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
  3. keepAliveTime:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。

虽然但是,通过设置allowCoreThreadTimeOut为true,即这个方法:executor.allowCoreThreadTimeOut(true);核心线程也会像额外的非核心线程一样,在空闲时根据keepAliveTime超时设置被回收。

  1. unit:单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间。参数 keepAliveTime 的时间单位有以下 7 种可选:

    • TimeUnit.DAYS:天
    • TimeUnit.HOURS:小时
    • TimeUnit.MINUTES:分
    • TimeUnit.SECONDS:秒
    • TimeUnit.MILLISECONDS:毫秒
    • TimeUnit.MICROSECONDS:微妙
    • TimeUnit.NANOSECONDS:纳秒
  2. workQueue:一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全。它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种,包含以下 7 种类型:

    • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
    • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
    • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
    • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
    • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
    • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关

  1. threadFactory:线程工厂,主要用来创建线程。

  2. handler:拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:

    • AbortPolicy:拒绝并抛出异常。
    • CallerRunsPolicy:使用当前调用的线程来执行此任务。
    • DiscardOldestPolicy:抛弃任务队列头部(最旧)的一个任务,并执行当前任务。
    • DiscardPolicy:忽略并抛弃当前任务。

默认策略为 AbortPolicy

四、线程池执行流程

  1. 如果当前运行的线程数小于核心线程数,那么就会新建一个线程来执行任务。
  2. 如果当前运行的线程数等于或大于核心线程数,但是小于最大线程数,那么就把该任务放入到任务队列里等待执行。
  3. 如果向任务队列投放任务失败(任务队列已经满了),但是当前运行的线程数是小于最大线程数的,就新建一个线程来执行任务。
  4. 如果当前运行的线程数已经等同于最大线程数了,新建线程将会使当前运行的线程超出最大线程数,那么当前任务会被拒绝,拒绝策略会调用RejectedExecutionHandler.rejectedExecution()方法。
    在这里插入图片描述

为什么要先放到任务队列,只有任务队列满了才会新建一个线程来执行任务?

  1. 资源管理:
    线程的创建和销毁是相对昂贵的操作,涉及操作系统资源的分配和回收。通过使用任务队列,可以避免频繁地创建和销毁线程,从而节省系统资源。
  2. 任务缓冲:
    任务队列作为缓冲区,可以暂时存储到来的任务,即使没有立即可用的线程来执行这些任务。这有助于平滑任务到达的峰值,避免因瞬间大量任务涌入而导致的系统过载。
  3. 并发控制:
    线程池通过限制线程的数量来控制并发级别。如果任务直接交给线程执行而不经过队列,可能会导致过多的线程同时运行,增加系统负载和上下文切换的开销,从而降低效率。

后言

希望本文可以让大家对自定义线程池有一个粗略的了解。如果有不对的地方欢迎大家指正。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要获取自定义线程池的线程,可以使用`ThreadPoolExecutor`类的`getActiveCount()`方法来获取当前线程池正在执行任务的线程数量,再使用`ThreadPoolExecutor`类的`getPoolSize()`方法来获取当前线程池的线程总数。代码示例如下: ```java import java.util.concurrent.*; public class CustomThreadPool { public static void main(String[] args) { // 创建自定义线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10) ); // 提交任务 for (int i = 0; i < 6; i++) { executor.execute(new Task(i)); } // 获取线程池正在执行任务的线程数量 int activeCount = executor.getActiveCount(); System.out.println("当前线程池正在执行任务的线程数量:" + activeCount); // 获取线程池的线程总数 int poolSize = executor.getPoolSize(); System.out.println("当前线程池的线程总数:" + poolSize); // 关闭线程池 executor.shutdown(); } } class Task implements Runnable { private int taskId; public Task(int taskId) { this.taskId = taskId; } @Override public void run() { System.out.println("任务 " + taskId + " 正在执行..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任务 " + taskId + " 执行完成!"); } } ``` 上述代码,`CustomThreadPool`类创建了一个自定义线程池`executor`,并提交了6个任务。然后使用`executor.getActiveCount()`方法和`executor.getPoolSize()`方法获取当前线程池的线程数量信息,并打印输出。最后关闭线程池。 ### 回答2: 在Java,我们可以通过以下步骤来获取自定义线程池的线程: 1. 首先,我们需要创建一个自定义线程池对象。可以使用ThreadPoolExecutor类来实现一个自定义线程池,该类提供了许多可调整的参数,例如核心线程数、最大线程数、闲置线程存活时间等等。 2. 在创建自定义线程池对象之后,我们可以通过调用execute()方法将任务提交到线程池。execute()方法接受一个Runnable对象作为参数,该对象代表一个待执行的任务。 3. 如果我们想获取线程池的线程,可以调用线程池对象的getPoolSize()方法,该方法返回当前线程池的线程数量。这可以帮助我们了解线程池的使用情况。 4. 另外,如果我们想获取线程池的每个线程的详细信息,可以通过调用线程池对象的getActiveThreads()方法来获取活动线程的数组。然后,我们可以遍历该数组以获取每个线程的相关信息,例如线程的ID、名称等等。 总结起来,要获取自定义线程池的线程,我们需要创建一个自定义线程池对象,然后通过调用相应的方法来获取线程池线程的数量或者每个线程的详细信息。这样,我们可以更好地了解线程池的使用情况,并且对线程池的调度和管理进行更精确的控制。 ### 回答3: Java获取自定义线程池的线程可以通过ThreadPoolExecutor类的getActiveCount()和getPoolSize()方法来实现。 首先,我们需要先创建一个自定义线程池对象,例如: ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); 其corePoolSize为线程池核心线程的数量,maximumPoolSize为线程池允许的最大线程数量,keepAliveTime为线程空闲时的存活时间,unit为存活时间的单位,workQueue为任务队列,用于存放待执行的任务。 接下来,我们使用executor对象可以调用getActiveCount()方法来获取当前活动的线程数量,即正在执行任务的线程数量。示例代码如下: int activeThreadCount = executor.getActiveCount(); 同时,我们还可以使用getPoolSize()方法来获取当前线程池的线程数量,包括核心线程和非核心线程。示例代码如下: int threadPoolSize = executor.getPoolSize(); 这样,我们就可以通过上述方法获得自定义线程池线程的数量了。 需要注意的是,使用线程池时要注意及时关闭线程池,以免引发线程泄漏或资源浪费的问题。可以通过调用executor.shutdown()方法来关闭线程池。 总之,我们可以通过ThreadPoolExecutor类的getActiveCount()和getPoolSize()方法来获取自定义线程池线程的数量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值