【Java基础】深入理解 Java 线程池

一、引言​

在 Java 多线程编程中,线程池是一个极为重要的工具。它通过复用已有的线程,减少线程创建和销毁的开销,从而显著提高应用程序的性能和资源利用率。本文将深入探讨 Java 线程池的各个方面,帮助读者全面掌握线程池的使用。​

二、线程池基础知识​

2.1 什么是线程池​

线程池可以理解为一个管理线程的 “池子”。它预先创建一定数量的线程并存储在池中,当有任务提交时,直接从线程池中取出线程来执行任务,任务完成后,线程并不会被销毁,而是返回线程池等待下一个任务。​

2.2 为什么使用线程池​

降低资源消耗:避免频繁创建和销毁线程带来的系统开销。创建线程需要分配内存、初始化栈等操作,这些操作在高并发场景下会消耗大量资源。​
提高响应速度:由于线程已预先创建,任务提交后能立即得到执行,无需等待线程创建过程。​
提高线程的可管理性:线程池可以对线程进行统一管理,如线程数量的控制、任务队列的管理等,便于监控和调优。​

2.3 线程池的核心组件​

线程池管理器:管理线程池的创建、销毁以及线程池状态的监控等。​
工作线程:线程池中的实际执行任务的线程,它们从任务队列中获取任务并执行。​
任务队列:用于存储等待执行的任务。当线程池中的线程都在忙碌时,新提交的任务会被放入任务队列中排队等待。​
任务拒绝策略:当任务队列已满且线程池中的线程达到最大数量时,新提交的任务会被拒绝,此时需要有相应的拒绝策略来处理这些被拒绝的任务。​

三、线程池的种类​

3.1 newFixedThreadPool​

创建一个固定大小的线程池,线程池中的线程数量始终保持不变。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则将任务放入任务队列中等待。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {final int task = i;​
    fixedThreadPool.submit(() -> {System.out.println(Thread.currentThread().getName() + " is handling task " + task);try {Thread.sleep(1000);} catch (InterruptedException e) {​
            e.printStackTrace();}});}​
fixedThreadPool.shutdown();

3.2 newCachedThreadPool​

创建一个可缓存的线程池,如果线程池中的线程在 60 秒内未被使用,将会被回收。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则创建一个新线程来执行任务。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int task = i;​
    cachedThreadPool.submit(() -> {System.out.println(Thread.currentThread().getName() + " is handling task " + task);try {Thread.sleep(1000);} catch (InterruptedException e) {​
            e.printStackTrace();}});}​
cachedThreadPool.shutdown();

3.3 newSingleThreadExecutor​

创建一个单线程的线程池,线程池中只有一个线程在工作。所有任务按照提交的顺序依次执行,相当于一个单线程的串行执行器。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int task = i;​
    singleThreadExecutor.submit(() -> {System.out.println(Thread.currentThread().getName() + " is handling task " + task);try {Thread.sleep(1000);} catch (InterruptedException e) {​
            e.printStackTrace();}});}​
singleThreadExecutor.shutdown();

3.4 newScheduledThreadPool​

创建一个支持定时及周期性任务执行的线程池。可以指定线程池的大小,线程池中的线程可以按照一定的延迟时间或周期来执行任务。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);​
scheduledThreadPool.schedule(() -> {System.out.println("This task is scheduled to run after 3 seconds.");}, 3, TimeUnit.SECONDS);​
scheduledThreadPool.scheduleAtFixedRate(() -> {System.out.println("This task is scheduled to run every 2 seconds.");}, 0, 2, TimeUnit.SECONDS);​
scheduledThreadPool.shutdown();

四、线程池实践​

4.1 自定义线程池​

在实际应用中,我们通常会根据具体需求来自定义线程池,而不是直接使用Executors工厂类创建的线程池。通过ThreadPoolExecutor类可以创建自定义线程池。

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, // 核心线程数​
        4, // 最大线程数​
        1, // 线程存活时间​
        TimeUnit.MINUTES, // 时间单位​
        new ArrayBlockingQueue<>(5), // 任务队列​
        Executors.defaultThreadFactory(), // 线程工厂​
        new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略​
);for (int i = 0; i < 10; i++) {final int task = i;​
    threadPoolExecutor.submit(() -> {System.out.println(Thread.currentThread().getName() + " is handling task " + task);try {Thread.sleep(1000);} catch (InterruptedException e) {​
            e.printStackTrace();}});}​
threadPoolExecutor.shutdown();

4.2 线程池监控​

为了确保线程池的正常运行和性能优化,对线程池进行监控是很有必要的。ThreadPoolExecutor类提供了一些方法来获取线程池的运行状态信息,如线程池中的活跃线程数、已完成任务数等。

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);// 获取活跃线程数​
int activeCount = executor.getActiveCount();// 获取已完成任务数​
long completedTaskCount = executor.getCompletedTaskCount();// 获取线程池中的线程数​
int poolSize = executor.getPoolSize();

五、线程池使用注意事项​

5.1 合理设置线程池参数​

核心线程数:应根据任务的类型和数量来设置。如果任务是 CPU 密集型的,核心线程数一般设置为 CPU 核心数或略少;如果是 I/O 密集型的,可以适当增加核心线程数,因为 I/O 操作会使线程阻塞,增加线程数可以充分利用 CPU 资源。​
最大线程数:要考虑系统的负载能力和资源限制,避免设置过大导致系统资源耗尽。​
任务队列:选择合适的任务队列类型和大小。如果任务队列过小,可能会导致任务频繁被拒绝;如果过大,可能会占用过多内存。​

5.2 避免死锁​

在使用线程池时,要注意避免死锁的发生。死锁通常是由于线程之间相互等待对方释放资源而导致的。在设计任务和线程池时,要确保资源的获取和释放顺序是合理的,避免出现循环等待的情况。​

5.3 正确处理任务异常​

当任务在执行过程中发生异常时,要确保有合适的异常处理机制。如果不处理异常,可能会导致线程池中的线程终止,影响任务的正常执行。可以通过实现Thread.UncaughtExceptionHandler接口来捕获线程执行过程中的异常。

Thread.setDefaultUncaughtExceptionHandler((t, e) -> {System.out.println("Thread " + t.getName() + " threw an exception: " + e.getMessage());​
    e.printStackTrace();});

5.4 优雅关闭线程池​

在应用程序结束时,要确保线程池能够优雅地关闭。调用shutdown()方法后,线程池会停止接受新任务,但会继续执行已提交到任务队列中的任务;调用shutdownNow()方法会尝试停止正在执行的任务,并立即清空任务队列。通常建议先调用shutdown()方法,等待一段时间后,如果线程池还未完全关闭,再调用shutdownNow()方法。

ExecutorService executorService = Executors.newFixedThreadPool(5);// 提交任务​
executorService.shutdown();try {if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {​
        executorService.shutdownNow();if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {System.err.println("Pool did not terminate");}}} catch (InterruptedException ie) {​
    executorService.shutdownNow();Thread.currentThread().interrupt();}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值