Java 线程池(Thread Pools)详解

这篇具有很好参考价值的文章主要介绍了Java 线程池(Thread Pools)详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1、线程池介绍

2、线程池执行原理

3、线程池中的阻塞队列

4、Java 线程池中的拒绝策略

5、Java 提供的创建线程池的方式

6、线程池的使用示例

7、ForkJoinPool 和 ThreadPool 的区别


1、线程池介绍

        线程池是一种重用线程的机制,用于提高线程的利用率和管理线程的生命周期,常用于多线程编程和异步编程。Java提供了多种线程池实现,其中最常用的是ThreadPoolExecutor类和Executors类提供的静态工厂方法。

        线程池由一个线程队列和一个任务队列组成,线程队列中保存着空闲线程,任务队列中保存着等待执行的任务。线程池启动后,线程池中的线程从任务队列中获取任务并执行,执行完毕后返回线程队列中等待下一次任务的到来。如果任务队列为空,线程池中的线程将等待新的任务到来或被关闭。

(1)Java中的线程池可以使用以下参数进行配置:

  1. 核心线程数(corePoolSize):线程池中的常驻线程数,即保持存活的线程数量。当任务数量小于核心线程数时,线程池中的线程数量不会减少,除非线程池被关闭。
  2. 最大线程数(maximumPoolSize):线程池中允许的最大线程数,即线程池中允许存在的最多线程数量。
  3. 任务队列(workQueue):用于保存等待执行的任务的队列,有多种实现方式,如ArrayBlockingQueue、LinkedBlockingQueue等。
  4. 线程存活时间(keepAliveTime):当线程池中的线程数量大于核心线程数时,多余的空闲线程的存活时间。如果空闲时间超过该值,多余的线程将被销毁,直到线程池中的线程数量等于核心线程数。
  5. 线程工厂(threadFactory):用于创建新线程的工厂,可以定制线程名、线程优先级等。
  6. 拒绝策略(rejectedExecutionHandler):当任务队列满时,对新任务的处理策略。有多种实现方式,如AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy等。

(2)线程池的主要优点包括:

  1. 降低线程创建和销毁的开销,提高系统性能。
  2. 提高线程的利用率和系统的吞吐量。
  3. 统一线程的管理和监控,避免线程泄漏和线程安全问题。
  4. 支持任务队列和拒绝策略等机制,提供灵活的任务调度和任务处理能力。

2、线程池执行原理

        线程池执行原理图示:

Java 线程池(Thread Pools)详解

3、线程池中的阻塞队列

        Java 线程池使用阻塞队列实现线程之间的同步,控制任务的提交和执行。线程池中的任务被提交到阻塞队列中,等待被线程池中的线程执行。当线程池中的线程空闲时,它们会从阻塞队列中取出任务进行执行。

        常用的阻塞队列有以下几种:

  1. ArrayBlockingQueue:基于数组实现的有界阻塞队列,插入操作和删除操作都可能会被阻塞。
  2. LinkedBlockingQueue:基于链表实现的阻塞队列,可以指定容量,如果未指定容量,则容量默认为 Integer.MAX_VALUE。插入操作和删除操作都可能会被阻塞。
  3. SynchronousQueue:一个没有容量的阻塞队列,插入操作和删除操作必须同时进行,否则会被阻塞。// 阻塞队列,不存储元素

        阻塞队列的实现可以保证线程安全,多个线程可以同时操作队列。当阻塞队列为空时,从队列中取出任务的操作会被阻塞,直到队列中有新的任务被添加进来。当阻塞队列已满时,添加任务的操作会被阻塞,直到队列中有任务被取出。

        在线程池中使用阻塞队列可以帮助控制任务的提交速度,防止任务提交过多导致系统资源的浪费。在使用阻塞队列时需要根据具体情况选择合适的实现类,以实现更高效的任务调度和执行。

        除定时执行的线程池外,其他三种线程池创建使用的阻塞队列如下图所示:

Java 线程池(Thread Pools)详解

        ArrayBlockingQueue和LinkedBlockingQueue的区别:

        ArrayBlockingQueue 底层采用数组来实现队列,因此它在创建时需要指定容量大小,并且容量不可变。由于是基于数组实现,因此 ArrayBlockingQueue 可以高效地随机访问队列中的元素,但是插入和删除操作需要移动元素,因此效率相对较低。// 适合随机访问

        LinkedBlockingQueue 底层采用链表来实现队列,因此它在创建时可以不指定容量大小,也可以指定容量大小,但是如果没有指定容量大小,则默认容量为 Integer.MAX_VALUE。由于是基于链表实现,因此 LinkedBlockingQueue 插入和删除元素时只需要修改指针,因此效率相对较高,但是不能高效地随机访问队列中的元素。// 适合频繁修改

4、Java 线程池中的拒绝策略

        Java 线程池中的拒绝策略是指当线程池中的工作队列已满,且线程池中的线程已达到最大值时,线程池无法再处理新的任务时所采取的策略。Java 线程池中提供了以下四种拒绝策略:

  1. AbortPolicy:直接抛出 RejectedExecutionException 异常,该异常可以在代码中捕获并进行处理。
  2. CallerRunsPolicy:在任务被拒绝添加后,会使用调用线程池的线程来执行该任务。
  3. DiscardOldestPolicy:将等待时间最长的任务丢弃,然后尝试将当前任务添加到工作队列中。
  4. DiscardPolicy:直接将任务丢弃,不作任何处理。

Java 线程池(Thread Pools)详解

        这些拒绝策略可以通过调用 ThreadPoolExecutor 类的 setRejectedExecutionHandler() 方法进行设置。例如,以下代码将线程池的拒绝策略设置为 AbortPolicy:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, // 核心线程数
    10, // 最大线程数
    60, // 空闲线程存活时间
    TimeUnit.SECONDS, // 时间单位
    new LinkedBlockingQueue<Runnable>(100), // 工作队列
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

        CallerRunsPolicy 拒绝策略:

        CallerRunsPolicy 拒绝策略,当线程池的工作队列已满且线程池中的线程数已达到最大值时,线程池无法再处理新的任务时,它会将该任务交给线程池的调用线程来执行,即使用提交任务的线程来执行该任务。

        CallerRunsPolicy 拒绝策略主要用于防止任务丢失和保证任务的顺序性。当线程池无法处理新的任务时,CallerRunsPolicy 会将任务交给调用线程来执行,这样可以确保任务不会丢失,并且可以保证任务的顺序性,即任务的执行顺序和提交顺序一致。

        但是,使用 CallerRunsPolicy 拒绝策略可能会导致调用线程被阻塞,因为调用线程需要等待任务执行完毕才能继续执行其他任务,这可能会影响整个系统的性能。因此,在选择拒绝策略时需要权衡任务执行的顺序性和系统的性能。

// 总的来说,Java自带的四种拒绝策略都很鸡肋,一般不用于生产,如何处理拒绝数据还需要根据各自的应用场景进行定制

        如何自定义线程池的拒绝策略:

        可以通过实现 RejectedExecutionHandler 接口来自定义线程池中的拒绝策略。该接口只有一个方法:

void rejectedExecution(Runnable r, ThreadPoolExecutor executor);

        当线程池无法处理新的任务时,会调用 rejectedExecution() 方法来处理拒绝的任务。在实现该方法时,可以根据实际情况进行相应的处理,比如将任务添加到其他队列中,或者记录日志等

        以下是一个自定义拒绝策略的示例代码:

public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 将拒绝的任务记录到日志中
        System.out.println("Task rejected, " + r.toString());
    }
}

        在上述代码中,CustomRejectedExecutionHandler 类实现了 RejectedExecutionHandler 接口,并重写了 rejectedExecution() 方法,当线程池无法处理新的任务时,该方法将被调用,并将拒绝的任务记录到日志中。

5、Java 提供的创建线程池的方式

        Java提供了多种创建线程池的方式,常见的方式包括:

  1. Executors类:Executors类是Java提供的线程池工具类,可以使用其提供的静态工厂方法来创建线程池,如newFixedThreadPool()、newCachedThreadPool()等。// IO密集行型
  2. ThreadPoolExecutor类:ThreadPoolExecutor是Java提供的线程池实现类,可以通过创建ThreadPoolExecutor对象来创建自定义的线程池。
  3. ForkJoinPool类:ForkJoinPool是Java提供的专门用于支持分而治之算法的线程池,可以通过创建ForkJoinPool对象来使用。// CPU密集型
  4. ScheduledExecutorService接口:ScheduledExecutorService是Java提供的用于定时执行任务的线程池接口,可以使用其实现类ScheduledThreadPoolExecutor来创建定时任务线程池。

        在选择创建线程池的方式时,应该根据具体的应用场景和需求来选择最合适的方式和参数配置。一般来说,如果需要执行大量的短期异步任务,可以使用newCachedThreadPool()创建可缓存线程池;如果需要执行一些固定的长期任务,可以使用newFixedThreadPool()创建固定大小的线程池;如果需要执行分而治之算法的任务,可以使用ForkJoinPool;如果需要执行定时任务,可以使用ScheduledExecutorService接口。同时,在使用线程池时,也应该注意线程安全、线程池参数配置等问题,以保证线程池的稳定和性能。

6、线程池的使用示例

        以下是一个使用 Java 线程池实现售票的示例:// 循环调用多次execute()方法

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TicketSeller {
    private static final int NUM_TICKETS = 1000; // 总票数
    private static int numSold = 0; // 已售票数
    private static ExecutorService executor = Executors.newFixedThreadPool(5); // 线程池

    public static void main(String[] args) {
        // 创建5个售票员线程
        for (int i = 0; i < 5; i++) {
            executor.execute(new TicketSellerTask());
        }
        executor.shutdown();
    }

    static class TicketSellerTask implements Runnable {
        @Override
        public void run() {
            while (numSold < NUM_TICKETS) {
                synchronized (TicketSeller.class) { // 同步块保证线程安全
                    if (numSold < NUM_TICKETS) {
                        System.out.println(Thread.currentThread().getName() + " sold ticket " + (++numSold));
                    }
                }
            }
        }
    }
}

        在这个示例中,我们使用了一个静态的 numSold 变量来记录已售票数。我们创建了一个固定大小为 5 的线程池,并创建了 5 个 TicketSellerTask 售票员任务。在 TicketSellerTask 中,我们使用一个同步块来保证线程安全,即同时只有一个线程能够访问临界区,从而避免了多个线程同时卖同一张票的问题。

        我们通过调用 executor.execute() 方法来提交任务,该方法会从线程池中选一个空闲线程来执行任务。最后,我们关闭了线程池。

7、ForkJoinPool 和 ThreadPool 的区别

        ForkJoinPool 和 ThreadPool 都是 Java 中常用的线程池实现,但它们有一些不同之处,下面列出一些区别:

  1. 工作原理不同:ThreadPool 是一个固定大小的线程池,用于执行可运行任务和可调度任务,而 ForkJoinPool 则是基于工作窃取算法的线程池,用于处理分治任务
  2. 任务调度方式不同:ThreadPool 中的任务是由一个任务队列来维护的,线程从队列中取出任务执行;而 ForkJoinPool 中的任务是由任务队列和工作窃取算法一起调度的,任务队列用于存储待执行任务,而工作线程会从其他线程的任务队列中窃取任务执行。
  3. 执行方式不同:ThreadPool 在执行任务时是按顺序依次执行的,而 ForkJoinPool 中的任务是以分治的方式执行的,一个大的任务会被分割成多个小任务,小任务会被分配给多个线程并行执行,然后将结果合并。

        所以,ForkJoinPool 更适用于处理分治任务,可以将大任务拆分成小任务并行执行,从而提高执行效率;而 ThreadPool 更适用于处理较小的任务,以及需要按顺序执行任务的场景。文章来源地址https://www.toymoban.com/news/detail-447580.html

到了这里,关于Java 线程池(Thread Pools)详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【多线程】Thread 类 详解

    继承 Thread 来创建一个线程类,并重写 run() 方法 创建 MyThread 类的实例 调用 start 方法启动线程 注意:只有调用 start 函数才真正的创建了一个线程。 更推荐使用这种方法, 因为 Runnable 只是描述了一个任务,至于任务通过进程、线程、线程池还是什么来执行的,Runnable 并不关

    2024年02月09日
    浏览(33)
  • 【C++】多线程(thread)使用详解

    多线程(multithreading),是指在软件或者硬件上实现多个线程并发执行的技术。具有多核CPU的支持的计算机能够真正在同一时间执行多个程序片段,进而提升程序的处理性能。在一个程序中,这些独立运行的程序片段被称为“ 线程 ”(Thread),利用其编程的概念就叫作“多线

    2024年02月14日
    浏览(33)
  • 多线程系列(二) -Thread类使用详解

    在之前的文章中,我们简单的介绍了线程诞生的意义和基本概念,采用多线程的编程方式,能充分利用 CPU 资源,显著的提升程序的执行效率。 其中 java.lang.Thread 是 Java 实现多线程编程最核心的类,学习 Thread 类中的方法,是学习多线程的第一步。 下面我们就一起来看看,创

    2024年02月19日
    浏览(36)
  • Jmeter系列-并发线程Concurrency Thread Group的介绍(7)

    Concurrency Thread Group提供了用于配置多个线程计划的简化方法,该线程组目的是为了保持并发水平,意味着如果并发线程不够,则在运行线程中启动额外的线程 Concurrency Thread Group提供了更好的用户行为模拟,因为它使您可以更轻松地控制测试的时间,并创建替换线程以防线程在

    2024年02月07日
    浏览(31)
  • 【.NET Core】 多线程之(Thread)详解

    线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果您的应用程序涉及复杂且耗时的操作,那么设置不同的线程执行路径往往事半功倍,让每个线程执行特定的工作任务。 线程是一个 轻量级进程 。一个使用线程的常见实例是操作系统中并行编程的实现

    2024年01月20日
    浏览(29)
  • 【STM32&RT-Thread零基础入门】 4. 线程介绍(理论)

    前文中的最后一个任务发现,一个main()函数很难同时实现按键功能和闪灯功能,就好像人很难同时完成左手画圆右手画方一样,这种情况可以安排一人去画圆、一人去画方,并行进行就很容易了,两人各司其职,互不干扰。 操作系统中,一个线程就像做事的一个人。一个操作

    2024年02月12日
    浏览(29)
  • Java多线程---线程的创建(Thread类的基本使用)

    本文主要介绍Java多线程的相关知识, Thread的创建, 常用方法的介绍和使用, 线程状态等. 文章目录 前言 一. 线程和Thread类 1. 线程和Thread类 1.1 Thread类的构造方法 1.2 启用线程的相关方法 2. 创建第一个Java多线程程序 3. 使用Runnable对象创建线程 4. 使用内部类创建线程 5. 使用Lamba

    2024年02月03日
    浏览(31)
  • GO语言:Worker Pools线程池、Select语句、Metex互斥锁详细示例教程

    技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点点赞收藏+关注谢谢支持 !!!

    2024年02月11日
    浏览(32)
  • Jmeter系列-阶梯加压线程组Stepping Thread Group详解(6)

    tepping Thread Group是第一个自定义线程组但,随着版本的迭代,已经有更好的线程组代替Stepping Thread Group了【Concurrency Thread Group】,所以说Stepping Thread Group已经是过去式了,但还是介绍一下 有预览图显示估计的负载; 可延迟启动线程组; 可持续增加线程负载; 可设置最大负载

    2024年02月09日
    浏览(33)
  • Java多线程 -Thread类的常用API

    Thread常用API说明 : Thread常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。 至于Thread类提供的诸如:yield、join、interrupt、不推荐的方法 stop 、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会在高级篇以及后续需要用到

    2024年02月21日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包