暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

面试题系列:什么是线程池?线程池ThreadPoolExecutor使用及其原理又是什么?

1024笔记 2023-05-31
100

作者平台:

| CSDN:https://blog.csdn.net/qq_41153943(ID:江夏、)

| 掘金:https://juejin.cn/user/651387938290686(ID:jiangxia_1024

| 知乎:https://www.zhihu.com/people/1024-paper-96(ID:江夏)

| GitHub:https://github.com/JiangXia-1024?tab=repositories

| 微信公众号:1024笔记

本文一共2496字,预计阅读15分钟
1、什么是Synchronized和ReentrantLock

Java中的线程(Thread)是指程序执行的一个路径,它是一种轻量级的进程,用于执行特定的任务。每个Java应用都至少有一个线程,即主线程,当Java应用启动时,主线程会自动开启。

Java中的线程是通过java.lang.Thread类来实现的。在Java中,创建线程主要有两种方式:

1. 1、继承Thread类:

- 定义一个继承Thread类的子类;

- 重写Thread类的run()方法,该方法是线程的入口点;

- 实例化该子类,并调用start()方法,start()方法将启动新的线程,并执行run()方法内的代码。

例如:

       public class MyThread extends Thread {
    @Override
    public void run() {
    // 线程要执行的代码
    }
    }


    // 创建并启动新线程
    MyThread thread = new MyThread();
    thread.start();

    1.2、 实现Runnable接口:

    - 定义一个实现Runnable接口的类;

    - 实现Runnable接口中的run()方法;

    - 创建该类的实例,并将其作为参数传递给Thread类的构造函数;

    - 调用start()方法来启动线程。

    例如:

         public class MyRunnable implements Runnable {
      @Override
      public void run() {
      // 线程要执行的代码
      }
      }
      // 创建Runnable对象
      MyRunnable runnable = new MyRunnable();
      // 创建并启动新线程
      Thread thread = new Thread(runnable);
      thread.start();

      在Java中,多线程的使用可以提高程序的执行效率,特别是对于一些需要大量计算或I/O等待的任务来说,使用多线程可以充分利用CPU资源,并提高程序响应速度。

      2、线程池

      Java中的线程池是一种可以管理和复用多个线程的机制。它主要包含两个组件:任务队列和线程池。任务队列用于存储等待执行的任务,而线程池则负责从任务队列中取出任务,创建线程并执行任务。

      在Java中,线程池通过 Executor 框架提供支持。Java中的Executor被设计成一个顶层接口,用于实现不同的线程调度策略。Executors类是Executor框架的工厂类,可以在应用程序中方便地创建和管理线程池。常见的线程池类型包括:

      1. FixedThreadPool:固定大小的线程池,线程数固定,线程池中的线程始终保持不变。如果某个任务进入任务队列后无法立即执行,则必须等待其他任务完成后才能执行。

      2. CachedThreadPool:可缓存的线程池,根据需要创建新线程,但在有空闲线程时会重用先前构建的线程。当线程池中的线程长时间未使用且满足回收条件时(JVM会自动判断),会被移出线程池,直到下次有任务需要执行时再重新创建新线程。

      3. SingleThreadExecutor:单一线程线程池,只有一个线程在工作,逐个执行任务。

      4. ScheduledThreadPool:具有定期执行任务和延迟执行任务的固定线程池大小。

      Java 中使用线程池的代码示例如下:

        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);
        // 创建一个需要执行的任务
        Runnable task = new Runnable() {
        @Override
        public void run() {
        // 执行一些操作,例如输出当前线程名字
        System.out.println("当前线程:" + Thread.currentThread().getName());
        }
        };
        // 提交任务到线程池中
        executor.submit(task);


        // 最后关闭线程池
        executor.shutdown();

        在上面的示例中,首先使用Executors.newFixedThreadPool()方法创建了一个固定大小为 10 的线程池。然后创建了一个需要执行的任务,它会输出当前线程名字。接着通过executor.submit()方法将任务提交给线程池处理。最后,使用 executor.shutdown()方法关闭线程池。

        使用线程池可以避免重复创建、销毁线程的开销,提高程序的性能。同时,线程池还可以帮助我们控制并发度,防止由于线程数量过多而造成系统负载过高的问题。线程池也可以灵活地配置一些参数,例如线程数、线程超时时间等,以最大限度地满足应用程序对线程处理的管理需求。

        3、线程池ThreadPoolExecutor原理

        Java中的线程池是通过 Executor 框架提供支持的,其中 ThreadPoolExecutor 是 Executor 的一个实现类。ThreadPoolExecutor 可以更加灵活地配置线程池的参数。

        ThreadPoolExecutor 主要有下面几个构造方法:

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

          它们都具有相同的核心参数:

          - corePoolSize:核心线程数。

          - maximumPoolSize:线程池最大同时执行的线程数。

          - keepAliveTime:空闲线程等待新任务的最长时间。

          - unit:keepAliveTime 参数的时间单位。

          - workQueue:阻塞队列,用于存储待执行的任务。

          - threadFactory:线程工厂,用于创建线程。

          - handler:拒绝策略,用于处理新任务提交时无法添加到阻塞队列中的情况。

          ThreadPoolExecutor 控制着多个并发执行的任务,可以将一个线程池分成三部分:

          1. 任务队列。当线程池中所有的线程均处于活动状态时,新任务将会在任务队列中等待。

          2. 线程池本身。ThreadPoolExecutor 实例维护着一个固定大小的线程数,这些线程已经被创建出来并且准备好接受新的任务。

          3. 任务拒绝处理器。当任务不能被添加到线程池或执行队列中时,将由任务拒绝处理器进行处理。

          ThreadPoolExecutor 的原理是:通过配置一些参数,可以灵活控制线程池中的线程数量、任务队列容量以及何时创建和销毁线程等操作,避免了频繁地创建、销毁线程带来的性能损失,并且可以更好地管理线程的开销和时间,达到提高程序性能和可维护性的目的。

          下面是 ThreadPoolExecutor 的使用示例:

            // 创建一个固定大小为10的线程池
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
            10, // 核心线程池大小
            20, // 最多允许20个线程同时执行
            60L, // 空闲线程等待新任务的最长时间
            TimeUnit.SECONDS, // 时间单位(秒)
            new ArrayBlockingQueue<>(1000), // 等待队列(容量为1000)
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者运行策略
            );


            // 提交任务到线程池中
            executor.submit(() -> {
            try {
            Thread.sleep(1000);
            System.out.println("Hello, world!");
            } catch (InterruptedException e) {
            e.printStackTrace();
            }
            });


            // 关闭线程池
            executor.shutdown();

            在上面的示例中,首先通过构造方法创建了一个固定大小为 10 的线程池executor,它最多允许 20 个线程同时执行,并且空闲线程等待新任务的最长时间为 60 秒。任务队列是一个容量为 1000 的有界阻塞队列。拒绝策略采用了CallerRunsPolicy,当线程池无法处理新任务时,会由提交该任务的主线程直接运行任务。

            接着,通过executor.submit()方法提交了一个任务到线程池中。这个任务会睡眠 1 秒并输出 "Hello, world!"。

            最后,通过executor.shutdown()方法关闭了线程池。

            4、总结

            在使用线程池时,需要根据具体的业务场景选择合适的线程池类型和参数,以达到更好地控制并发度、提高程序性能等目的。

            最后感谢大家的关注!

            -END-


            相关推荐:


                             我就知道你在看!

            文章转载自1024笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

            评论