真相!线程池简单介绍和代码演示?

什么是线程池?

在编写多线程应用程序时,需要管理线程的生命周期和数量。线程池就是一种管理和重用线程的技术,通过线程池可以优化线程的创建和销毁过程,提高线程的复用率,避免资源的浪费和频繁的上下文切换。线程池维护一组线程,当有任务到来时,从线程池中选取一个空闲线程执行任务,当线程执行完任务后,线程并不会被销毁,而是返回到线程池中等待下一次任务的分配。线程池通常包含以下几个组成部分:

  • 任务队列:用于存放任务的队列。
  • 线程池管理器:用于管理线程池中的线程,包括线程的创建、销毁、启动和停止等操作。
  • 工作线程:线程池中的线程,用于执行任务。
  • 任务接口:定义任务的接口,通常是一个Runnable接口。

Java中的线程池

在Java中,线程池的实现是通过java.util.concurrent包下的ThreadPoolExecutor类来完成的。ThreadPoolExecutor是一个灵活、可配置的线程池实现,它可以根据需要调整线程池中的线程数量,支持任务队列、线程工厂、拒绝策略等功能。ThreadPoolExecutor的构造方法如下:

public 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类实现,通过submit方法提交任务到线程池中执行。

import java.util.concurrent.*;

public class ThreadPoolExample {

    // 定义任务接口
    public interface Task {
        void execute();
    }

    // 线程池管理器
    private static class ThreadPoolManager {
        private static ThreadPoolExecutor executor;

        // 初始化线程池
        public static void init() {
            executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy());
        }

        // 提交任务到线程池
        public static void submit(Task task) {
            executor.submit(() -> task.execute());
        }

        // 关闭线程池
        public static void shutdown() {
            executor.shutdown();
        }
    }

    // 测试代码
    public static void main(String[] args) {
        // 初始化线程池
        ThreadPoolManager.init();

        // 提交任务到线程池
        for (int i = 0; i < 10; i++) {
            int taskNo = i;
            ThreadPoolManager.submit(() -> {
                System.out.println("Task " + taskNo + " executed by " + Thread.currentThread().getName());
            });
        }

        // 关闭线程池
        ThreadPoolManager.shutdown();
    }
}

在上面的示例中,我们定义了一个任务接口Task,包含一个execute方法,用于定义任务的执行逻辑。我们通过ThreadPoolManager类来管理线程池,通过ThreadPoolExecutor类来创建线程池,submit方法用于提交任务到线程池中执行,shutdown方法用于关闭线程池。

运行上面的代码,输出如下:

Task 0 executed by pool-1-thread-1
Task 1 executed by pool-1-thread-2
Task 2 executed by pool-1-thread-1
Task 3 executed by pool-1-thread-2
Task 4 executed by pool-1-thread-3
Task 5 executed by pool-1-thread-1
Task 6 executed by pool-1-thread-2
Task 7 executed by pool-1-thread-3
Task 8 executed by pool-1-thread-1
Task 9 executed by pool-1-thread-2

从输出结果可以看出,线程池中创建了2个核心线程和2个非核心线程,分别执行了10个任务。线程池中的线程由线程池管理器管理,任务队列使用LinkedBlockingQueue实现,拒绝策略采用了AbortPolicy,即当任务队列已满且线程数已经达到最大值时,抛出RejectedExecutionException异常。

线程池是一种优化多线程应用程序的技术,可以有效地管理和重用线程,提高线程的复用率和程序的执行效率。在Java中,线程池的实现是通过ThreadPoolExecutor类来完成的,可以根据需要调整线程池中的线程数量,支持任务队列、线程工厂、拒绝策略等配置,能够满足不同场景下的线程池需求。使用线程池还能避免线程过多导致系统资源不足的问题,从而提高程序的稳定性。

在使用线程池时,需要注意线程池的配置和使用方式,避免出现线程池耗尽资源、任务执行效率低下等问题。一般情况下,我们可以根据业务需求和系统资源情况来调整线程池的核心线程数、最大线程数和任务队列等参数,从而达到最优的线程池配置。

最后,需要注意的是,在Java 8中,已经引入了CompletableFuture类,它可以方便地实现异步任务的执行和结果处理,对于一些简单的异步任务,我们可以考虑使用CompletableFuture来替代线程池的使用。

补充使用注意事项

1、避免使用Executors工具类创建线程池

Java提供了Executors工具类,可以方便地创建各种类型的线程池,但是这些线程池的默认配置可能不适合当前应用程序的场景。例如,Executors.newCachedThreadPool()创建的是一个具有自动回收机制的线程池,线程数可以无限制地增长,如果应用程序中有大量的短期任务,就会导致线程数不断增加,从而耗尽系统资源。

因此,在实际应用程序中,建议根据业务需求和系统资源情况,手动配置线程池参数,避免使用Executors工具类创建默认配置的线程池。

2、控制线程池的并发数

线程池中的线程数量对于应用程序的性能和稳定性都有重要的影响。如果线程数过多,会导致系统资源不足,从而降低应用程序的性能;如果线程数过少,会导致任务阻塞和等待,从而影响应用程序的响应时间和稳定性。

因此,需要根据业务需求和系统资源情况,控制线程池的并发数,避免线程数过多或过少。可以通过调整线程池的核心线程数、最大线程数和任务队列等参数,达到最优的线程池配置。

3、避免任务阻塞和等待

线程池中的任务队列是一个关键的部分,它决定了线程池中任务的调度方式和执行顺序。如果任务队列满了,线程池会根据设置的拒绝策略进行处理。如果拒绝策略是AbortPolicy,那么当任务队列满了且线程数已经达到最大值时,新提交的任务会被直接拒绝并抛出RejectedExecutionException异常;如果拒绝策略是CallerRunsPolicy,那么当任务队列满了且线程数已经达到最大值时,新提交的任务会在提交线程中执行。

因此,在使用线程池时,需要避免任务阻塞和等待,尽量让任务队列不要满,从而避免出现拒绝策略的情况。

4、对于长时间执行的任务,考虑异步执行

对于一些长时间执行的任务,例如网络请求、IO操作等,可以考虑使用异步执行的方式,避免阻塞线程池中的线程。可以使用CompletableFuture或者异步线程池等方式来实现异步执行,从而提高应用程序的性能和稳定性。

5、线程池的生命周期

线程池的生命周期包括创建、运行和关闭三个阶段。在创建线程池时,可以通过构造函数或者set方法设置线程池的配置参数;在运行阶段,线程池会接收并执行任务;在关闭阶段,线程池会停止接收新任务,等待所有已经提交的任务执行完成后,关闭线程池中的所有线程。

需要注意的是,在关闭线程池时,应该调用线程池的shutdown()方法,该方法会等待所有任务执行完成后,关闭线程池中的所有线程;如果希望立即关闭线程池并取消所有任务的执行,可以调用线程池的shutdownNow()方法。

6、线程池的异常处理

线程池中的任务可能会抛出异常,例如NullPointerException、RuntimeException等。如果任务抛出异常,线程池会使用默认的UncaughtExceptionHandler处理异常,通常情况下,这些异常信息只是简单地输出到控制台上,无法进行有效的处理。

因此,在使用线程池时,应该自定义UncaughtExceptionHandler,对异常进行有效的处理,例如记录日志、发送邮件等。

7、线程池的监控和调试

在实际应用程序中,需要对线程池进行监控和调试,以保证线程池的正常运行。可以使用Java自带的JMX技术来监控线程池的状态,例如线程池中的线程数、任务队列中的任务数、任务的执行情况等;还可以使用一些第三方工具,例如VisualVM、JProfiler等,对线程池进行调试和分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值