Java池化思想之一:线程池(Thread Pool)

在 Java 中,池化结构(Pooling Structure)是一种常用的设计模式,用于管理和重复使用有限的资源。这种结构通过预先创建一定数量的资源对象(如线程、数据库连接、对象等),然后将这些对象集中管理并分配给请求者使用。在使用完毕后,这些资源对象不会被销毁,而是返回到池中,供下一个请求者再次使用。

池化结构在 Java 中广泛应用于各类需要频繁创建和销毁资源的场景中。通过重复使用资源对象,池化结构能够有效提高资源的使用率、控制资源的数量,并提升系统的性能和稳定性。在开发中,合理设计和配置池化结构,对系统的高效运行至关重要。

池化结构的作用

  1. 提高资源使用率

    • 池化结构通过重复使用资源对象,避免了频繁的资源创建和销毁操作。这不仅减少了资源分配和释放的开销,还提高了资源的使用效率。例如,通过线程池可以复用线程,从而避免了频繁创建和销毁线程带来的性能损耗。

  2. 控制资源的数量

    • 池化结构通常允许设置资源对象的最大数量,从而避免系统中出现过多的资源实例,导致资源枯竭或性能下降。例如,数据库连接池通过限制连接的最大数量,防止数据库因过多连接而崩溃。

  3. 提高系统性能和稳定性

    • 由于资源的复用,池化结构能够显著减少资源分配的时间,从而提高系统的响应速度。此外,池化结构还可以通过合理配置池的大小和参数,防止系统资源被过度消耗,提高系统的稳定性。

  4. 简化资源管理

    • 池化结构通常包含资源的管理逻辑,如资源的初始化、复用、回收、超时处理等。通过统一的管理方式,池化结构可以简化资源的管理工作,减少开发人员的负担。

线程池(Thread Pool)

线程池(Thread Pool)是 Java 中一种非常重要的多线程并发编程机制。通过使用线程池,你可以更有效地管理和重用线程资源,而不是每次执行任务时都创建和销毁线程。

线程池的工作原理

线程池的基本思想是:预先创建一定数量的线程,这些线程处于待命状态,等待执行任务。当有任务提交到线程池时,线程池将任务分配给空闲的线程去执行。如果所有线程都在执行任务,线程池可以根据配置采取不同的策略,比如等待、拒绝任务或创建新的线程等。

线程池的主要优势

  1. 减少资源开销:创建和销毁线程是有代价的,特别是在高并发环境下。通过线程池,可以重用已创建的线程,减少资源开销。

  2. 提高响应速度:任务到达时无需等待线程创建,可以直接执行任务。

  3. 方便管理:线程池可以统一管理线程,设置线程的最大并发数量,避免系统因线程过多而资源耗尽。

Java 中的线程池实现

Java 中 java.util.concurrent 包提供了 Executor 框架,方便地创建和管理线程池。常用的线程池类有:

1. 固定大小线程池 (FixedThreadPool)

Executors.newFixedThreadPool(int nThreads) 创建一个固定大小的线程池,线程池中的线程数量始终不变。

用途

  • 固定大小线程池创建一个包含固定数量线程的线程池。这些线程在整个应用程序的生命周期中会一直存在,直到线程池被显式关闭。

特点

  • 在这个线程池中,线程数是固定的,即使有大量任务需要执行,线程池也只会使用固定数量的线程来处理任务。当所有线程都在忙碌时,其他任务会进入队列等待执行。

适用场景

  • 适用于需要控制并发任务数量的场景,避免系统因资源消耗过多而变得不稳定。例如,服务器应用中控制同时处理的请求数量。

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小为3的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
​
        // 提交5个任务到线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            fixedThreadPool.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " completed.");
            });
        }
​
        // 关闭线程池
        fixedThreadPool.shutdown();
    }
}

作用

  • 适用于有固定数量线程的场景,限制并发线程的数量。

  • 常用于需要控制并发任务数量的应用场景。

终端输出

Task 1 is running on thread pool-1-thread-1
Task 2 is running on thread pool-1-thread-2
Task 3 is running on thread pool-1-thread-3
Task 1 completed.
Task 2 completed.
Task 3 completed.
Task 4 is running on thread pool-1-thread-1
Task 5 is running on thread pool-1-thread-2
Task 4 completed.
Task 5 completed.

说明:线程池中有3个线程,先执行前3个任务,前3个任务完成后,线程池继续执行后续的任务。

2. 缓存线程池 (CachedThreadPool)

Executors.newCachedThreadPool() 创建一个可缓存的线程池,线程池大小不固定,可以根据需求动态创建新线程。适用于大量短生命周期的任务。

用途

  • 缓存线程池是一个大小不固定的线程池,它可以根据需要动态创建新的线程。当有新的任务到达时,如果有空闲线程则使用空闲线程执行任务,如果没有则创建新线程。

特点

  • 线程池中线程数几乎不受限制,通常适合处理大量短期的小任务。缓存线程池在执行完任务后会回收线程并将其存入池中,当池中线程空闲超过一定时间(通常是60秒)后,线程会被终止并从池中移除。

适用场景

  • 适用于负载波动较大、任务执行时间短的场景,例如处理并发数量不确定的即时任务请求。它能有效地应对瞬时的大量请求。

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class CachedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个缓存线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
​
        // 提交5个任务到线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            cachedThreadPool.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " completed.");
            });
        }
​
        // 关闭线程池
        cachedThreadPool.shutdown();
    }
}

作用

  • 创建一个可根据需要动态增加线程数量的线程池。

  • 适用于执行大量短期任务或负载变化较大的场景。

终端输出

Task 1 is running on thread pool-1-thread-1
Task 2 is running on thread pool-1-thread-2
Task 3 is running on thread pool-1-thread-3
Task 4 is running on thread pool-1-thread-4
Task 5 is running on thread pool-1-thread-5
Task 1 completed.
Task 2 completed.
Task 3 completed.
Task 4 completed.
Task 5 completed.

说明:由于缓存线程池会根据需要创建新线程,因此所有任务几乎是同时启动并执行的

3. 单线程池 (SingleThreadExecutor)

Executors.newSingleThreadExecutor() 创建一个单线程的线程池,这个线程池中只有一个线程在工作,所有任务被保证按顺序执行。

用途

  • 单线程池是一个只有一个工作线程的线程池。这个线程池保证所有任务按提交顺序逐一执行,在同一时刻最多只有一个任务在执行。

特点

  • 这个线程池确保所有任务按照顺序执行,任务之间不会有并发问题。即使线程因某种原因意外终止,也会有一个新的线程替代它继续执行后续任务。

适用场景

  • 适用于需要保证任务顺序执行的场景,例如需要顺序写入文件或顺序处理网络请求等应用场景。

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        // 创建一个单线程的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
​
        // 提交5个任务到线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            singleThreadExecutor.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " completed.");
            });
        }
​
        // 关闭线程池
        singleThreadExecutor.shutdown();
    }
}

作用

  • 用于保证任务按顺序执行的场景,适用于不需要并发、但要顺序执行任务的应用。

终端输出

Task 1 is running on thread pool-1-thread-1
Task 1 completed.
Task 2 is running on thread pool-1-thread-1
Task 2 completed.
Task 3 is running on thread pool-1-thread-1
Task 3 completed.
Task 4 is running on thread pool-1-thread-1
Task 4 completed.
Task 5 is running on thread pool-1-thread-1
Task 5 completed.

说明:单线程池中只有一个线程,因此任务是按顺序依次执行的。

4. 定时线程池 (ScheduledThreadPool)

Executors.newScheduledThreadPool(int corePoolSize) 创建一个定时执行或周期性执行任务的线程池。

用途

  • 定时线程池用于定时执行任务或周期性地执行任务。它允许任务在指定延迟后执行或以固定的时间间隔重复执行。

特点

  • 定时线程池在延迟或周期性任务处理方面非常有效。它在初始化时创建固定数量的线程,这些线程可用于处理定时或周期任务。它能很好地支持基于时间的任务调度,如定时报告生成、定期清理任务等。

适用场景

  • 适用于需要定期或定时执行任务的场景,例如定时备份、定期发送邮件通知、或定时清理过期数据等。

示例代码

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
​
public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个定时线程池,核心线程数为2
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
​
        // 提交定时任务,延迟3秒执行
        scheduledThreadPool.schedule(() -> {
            System.out.println("Task 1 is running on thread " + Thread.currentThread().getName());
        }, 3, TimeUnit.SECONDS);
​
        // 提交周期性任务,延迟1秒后每2秒执行一次
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            System.out.println("Task 2 is running on thread " + Thread.currentThread().getName());
        }, 1, 2, TimeUnit.SECONDS);
​
        // 10秒后关闭线程池
        scheduledThreadPool.schedule(() -> scheduledThreadPool.shutdown(), 10, TimeUnit.SECONDS);
    }
}

作用

  • 适用于需要延时执行或周期性执行任务的场景,如定时任务调度。

终端输出

Task 2 is running on thread pool-1-thread-1
Task 1 is running on thread pool-1-thread-2
Task 2 is running on thread pool-1-thread-1
Task 2 is running on thread pool-1-thread-1
Task 2 is running on thread pool-1-thread-1

说明:第一个任务延迟3秒执行,第二个任务延迟1秒后每2秒执行一次,持续到线程池关闭。

5. 自定义线程池 (ThreadPoolExecutor)

用途

  • 自定义线程池提供了对线程池参数的完全控制。开发者可以根据应用的具体需求定制线程池的行为,包括核心线程数、最大线程数、线程空闲时间、任务队列类型等。

特点

  • 自定义线程池是最灵活的线程池实现,允许开发者设置线程池的所有参数。例如,可以定义核心线程数以处理常见负载,设置最大线程数以应对突发流量,并配置任务队列来处理排队任务。它还可以配置线程的存活时间,以便在任务完成后适时释放资源。

适用场景

  • 适用于复杂应用场景下的精细化资源管理。例如,在一个高性能服务器中,可能需要精确控制线程的创建、销毁以及任务调度策略,从而优化资源利用率并保证系统稳定性。

示例代码

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
public class CustomThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个自定义的线程池,核心线程数为2,最大线程数为4,队列大小为2
        ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
                2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2));
​
        // 提交6个任务到线程池
        for (int i = 1; i <= 6; i++) {
            final int taskId = i;
            customThreadPool.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " completed.");
            });
        }
​
        // 关闭线程池
        customThreadPool.shutdown();
    }
}

作用

  • 提供了完全的配置灵活性,允许根据应用需求自定义线程池的参数。

终端输出

Task 1 is running on thread pool-1-thread-1
Task 2 is running on thread pool-1-thread-2
Task 3 is running on thread pool-1-thread-3
Task 4 is running on thread pool-1-thread-4
Task 1 completed.
Task 5 is running on thread pool-1-thread-1
Task 2 completed.
Task 6 is running on thread pool-1-thread-2
Task 3 completed.
Task 4 completed.
Task 5 completed.
Task 6 completed.

说明:前两个任务使用核心线程执行,任务3和任务4进入队列,后续两个任务启动新的线程执行。

线程池的核心参数

  • Java线程池提供了一些常见的参数,用于配置线程池的行为和性能

    1. corePoolSize(核心池大小):标识线程池中核心线程的数量,核心线程会一致存活,即使没有任务需要执行,默认情况下,核心池的大小为0

    2. maximumPoolSize(最大池大小):标识线程池中最大线程的数量,当工作队列已满并且池中的线程数小于最大池大小的时候,线程池会创建新的线程来处理任务,直到达到最大池的大小。默认情况下,最大池大小为Inter.MAX_VALUE。

    3. keepAliveTime(线程空闲时间):表示当线程池中的线程数量超过核心吃大小的时候,空闲线程的存活时间,当前程空闲时间超过keepAlive时,多余的线程会被销毁,直到线程池中的线程数等于核心池大小。默认情况下,空闲线程会被立即销毁

    4. unit(事件单位):用于指定keepAliveTime的事件单位,可以是纳秒(TimeUnit.NANOSECONDS)、微秒(TimeUnit.MICROSECONDS)、亳秒(TimeUnit.MILLISECONDS)、秒(TimeUnit.SECONDS)、分钟(TimeUnit.MINUTES)、小时(TimeUnit.HOURS)、天(TimeUnit.DAYS)

    5. workQueue(工作队列):用于存储等待执行的任务。Java提供了多种类型的工作队列,如SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue等。默认情况下,使用无界队列LinkedBlockingQueue

    6. threadFactory(线程工厂):用于创建新线程,可以自定义线程工厂来指定线程的命名规则,优先级等

    7. handler(拒绝策略):用于处理线程池队列已满且无法接收新任务时候的情况。java提供了几种内置的拒绝策略,如AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。也,也可以自定义

线程池的生命周期管理

线程池具有明确的生命周期管理方法,可以在不同状态下对其进行操作:

  • shutdown():启动线程池的有序关闭流程,已提交的任务会继续执行,但不再接受新任务。

  • shutdownNow():尝试停止所有正在执行的任务,并返回等待执行的任务列表。

  • awaitTermination():在关闭线程池后,等待所有任务完成或超时。

管理好线程池的生命周期可以防止资源泄漏,确保系统在关闭时能够正确地释放资源。

线程池的拒绝策略

当线程池中的任务数量达到最大值,并且任务队列已满时,线程池需要决定如何处理新的任务。Java 提供了四种默认的拒绝策略:

  1. AbortPolicy(默认):直接抛出 RejectedExecutionException 异常。

  2. CallerRunsPolicy:由提交任务的线程执行该任务。

  3. DiscardPolicy:直接丢弃任务,不给予任何处理。

  4. DiscardOldestPolicy:丢弃队列中最早的任务,然后尝试重新提交当前任务。

线程池化的阻塞队列

线程池化的阻塞队列(Thread Pool with Blocking Queue)是线程池与阻塞队列的结合,常用于生产者-消费者模式中。阻塞队列用于存储等待执行的任务,而线程池中的线程则不断从队列中取出任务执行。

  1. ArrayBlockingQueue数组阻塞队列

    • 任务存储在一个固定大小的数组中。

    • ArrayBlockingQueue

      • 特性

        • 基于数组的有界阻塞队列,队列长度固定,支持公平和非公平锁机制。

        • 一旦队列达到最大容量,生产者线程将被阻塞,直到有任务被消费。

      • 适用场景

        • 适合于任务数量有明确上限的场景,防止因过多任务堆积导致内存溢出。

  2. LinkedBlockingQueue链表阻塞队列

    • 任务以链表的形式存储,可以动态扩展。

    • 特性

      • 基于链表的阻塞队列,默认是无界的,但可以设置上限。

      • 因为没有固定大小的数组限制,所以任务存储能力更强,且吞吐量较高。

      适用场景

      • 适合任务流量较大且不希望丢失任务的场景,可以提供更高的并发吞吐量。

  3. SynchronousQueue同步队列

    • 任务直接从生产者传递到消费者,不会存储任务。

    • 特性

      • 没有任何内部容量的阻塞队列,不能存储任务,生产者线程提交的任务必须直接交给消费者线程处理。

      • 如果没有空闲线程来接收任务,生产者线程会被阻塞。

      适用场景

      • 适合每个任务必须立即处理的场景,或在需要通过增加线程来应对高负载的情况下使用。

  4. PriorityBlockingQueue优先级阻塞队列

    • 任务根据优先级排序,优先级高的任务先处理。

    • 特性

      • 一个无界的优先级队列,任务根据优先级而不是插入顺序进行排序。

      • 当有多个任务时,高优先级的任务会被优先处理。

      适用场景

      • 适合需要按优先级处理任务的场景,例如调度系统或需要按重要性排序的任务处理。

  5. DelayQueue延迟队列

    • 任务需要等待一段时间后才能执行,常用于定时任务。

    • 特性

      • 基于优先级队列的特殊队列,任务只有在其延迟时间到达之后才能被取出执行。

      适用场景

      • 适合需要延迟执行任务的场景,例如定时任务调度、延迟消息队列等。

线程池中的阻塞队列选择

  • ArrayBlockingQueue vs LinkedBlockingQueue

    • ArrayBlockingQueue 适合固定任务数量且需要限制资源使用的情况;

    • LinkedBlockingQueue 则更适合任务数量不确定且需要高并发处理的情况。

  • SynchronousQueue 的使用通常会导致线程池以最大并发来执行任务,因为它不存储任务,这在需要快速处理大量短任务时非常有效。

  • PriorityBlockingQueueDelayQueue 则更为特殊,适用于需要优先级调度和定时执行的任务。

选择合适的阻塞队列可以优化线程池的性能,保证系统的高效稳定运行。开发者应根据具体的任务需求、系统负载特点以及处理任务的紧急性来选择合适的阻塞队列。

你可能感兴趣的:(java,oracle,数据库)