ThreadPoolExector介绍

本文详细解析Java线程池ThreadPoolExecutor的核心概念、构造方法、线程创建与任务提交条件、线程数目的维护策略、任务入队列与丢弃原则,并介绍Executor、ExecutorService接口及Executors类提供的静态方法。通过实例分析,帮助开发者掌握如何高效利用线程池提升并发编程效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 Executor接口

public interface Executor {
    void execute(Runnable command);
}

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable>shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;
    <T> Future<T>submit(Callable<T> task);
    <T> Future<T>submit(Runnable task, T result);
    Future<?>submit(Runnable task);
    <T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException;
    <T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks,long timeout, 
            TimeUnit unit)throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

public abstract class AbstractExecutorService implements ExecutorService {
    ....
}

二、 ThreadPoolExecutor

public class ThreadPoolExecutor extends AbstractExecutorService {
    ...
}

构造方法:

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

corePoolSize -- 线程池维护的核心线程数,即使它们处于空闲状态;线程可以预先创建或延迟创建,如果是延迟创建,则当有任务来临时才会创建;

maximumPoolSize -- 线程池中允许存在的最大线程数;

keepAliveTime -- 超额线程的空闲存活时间;默认情况下,当线程池中的线程个数超出了corePoolSize,那么空闲的线程一旦超出额定的存活时间就会被终止,以节省系统资源;

unit -- 参数keepAliveTime的单位;

workQueue -- 通过execute()提交的任务(Runnable)在被执行前,保存在workQueue中;

threadFactory -- 当需要创建一个新的线程时,使用这个参数来创建;

handler -- 当线程阻塞或线程数量达到workQueue的上限时,使用这个handler来处理。

1. 线程创建和任务提交的条件和步骤

当一个新任务被提交给ThreadPoolExecutor的时候,处理流程大概是这样的:

(1)如果当前线程池中运行的线程数目低于corePoolSize,则创建新线程执行提交的任务,而无需检查当前是否有空闲线程;

(2) 如果提交任务时线程池中线程数已大于等于corePoolSize,则将提交的任务加入等待执行队列;

(3) 如果提交任务时等待执行的任务队列是有限队列,而且已满,或者无法将任务添加到队列中(SynchronousQueue),则在线程池中开辟新线程执行此任务;

(4) 如果线程池中线程数目已达到maximumPoolSize,则提交的任务交由RejectedExecutionHandler处理。

默认情况下,ThreadPoolExecutor的线程数是根据需求来延迟初始化的,即有新任务加进来的时候才会挨个创建线程。

除此之外,线程池执行器也提供了提前创建初始化线程的方法:

public boolean prestartCoreThread()

public int prestartAllCoreThreads()

分别是预先创建一个线程和预先创建线程直到线程数到达核心线程数corePoolSize。

2. 线程数目的维护

刚刚提到,ThreadPoolExecutor有corePoolSize和maximum两个变量来维护线程池中的线程个数,提交任务的时候会有线程数目的增长,那线程的个数又是怎么来维护的呢。构造方法里还有两个参数,分别是keepAlive和unit,这两个参数确定了一个时间间隔,也就是空闲线程存活的时间间隔。默认情况下,当线程池中的线程个数超出了corePoolSize,那么空闲的线程一旦超出额定的存活时间就会被终止,以节省系统资源。在JDK1.6之后,增加了allowsCoreThreadTimeOut这个boolean属性和读写属性的方法,用来标志核心线程空闲超时是否也可以终止掉。如果allowCoreThreadTimeout为true, 则核心线程如果空闲超时时也会被终止,直到线程数为0。

2. 线程入队列和任务丢弃原则简述

除了前面描述涉及到的四个属性和ThreadFactory之外,还有两个分别是workQueue和handler,分别是BlockingQueue和RejectedExecutionHandler类型。

BlockingQueue只是一个接口,它所表达的是当队列为空或者已满的时候,需要阻塞以等待生产者/消费者协同操作并唤醒线程。其有很多不同的具体实现类,各有特点。有的可以规定队列的长度,也有一些则是无界的。

排队策略:

2.1  SynchronousQueue(直接提交):CachedThreadPool使用的是这个BlockingQueue,它将任务直接提交给线程而不保持它们。如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。通常要求无界 maximumPoolSizes以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

    适用场景:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁

2.2 LinkedBlockingQueue(无界队列):FixedThreadPool和SingleThreadExecutor使用的是这个BlockingQueue,当所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过corePoolSize因此,maximumPoolSize 的值也就无效了。

    适用场景:当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列

2.3  ArrayBlockingQueue(有界队列):当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 

当然,开发者也可以定制ThreadPoolExecutor时使用ArrayBlockingQueue有界队列。

对于任务丢弃,ThreadPoolExecutor以内部类的形式实现了4个策略。分别是:

(1)CallerRunsPolicy:提交任务的线程自己负责执行这个任务。即不会创建新的线程,就在主线程中执行这个任务;

(2)AbortPolicy:使Executor抛出异常,通过异常做处理。

(3)DiscardPolicy:丢弃请求被拒绝的任务(discards therejected task)。

(4)DiscardOldestPolicy:丢弃掉队列中最早加入的任务,即后面的任务会挤走前面的任务。从而最终被执行的是末尾提交的任务。

在调用构造方法时,参数中未指定RejectedExecutionHandler情况下,默认采用AbortPolicy。

Executors类的几个静态方法:

/**
 * 适用场景:保证先提交的任务先执行。
 * 每次提交任务时,如果没有空闲的线程,才会创建1个新线程。如果有某一线程超过
 * 60s处于空闲状态,则将它移除。这种线程池可能没有线程存在,因此能节省资源,提
 * 高性能,适合生命周期短(short-lived)的异步任务。SynchronousQueue的意思是每个插入
 *操作必须等待另一个线程的对应移除操作,就是缓冲区为1的生产者消费者模式。
 */
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>());
}

/**
 * 创建一个线程池,维持最多nThreads个线程,如果有多余的任务,则会在无界队列
 * LinkedBlockingQueue中等待,直到有1个线程可用(available)。若某一线程在执行期间
 * 由于某种原因而执行失败,先于shutdown就terminates,则1个新的线程被创建执行接
 * 下来的工作。
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());
}


/**
 * 创建一个线程池,只有不多于1个线程处于active状态,剩下的任务在无界队列
 *  LinkedBlockingQueue排队。若该线程在执行期间由于某种原因而执行失败,
 *  先于shutdown就terminates,则1个新的线程被创建来取代它。
 *  该方法与newFixedThreadPool(1)不同,后者是可以重新配置更改的,而前者则不能。
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

<think>嗯,我现在要理解Java的ThreadPoolExecutor中的ctl参数的作用。首先,我需要回忆一下线程池的基本概念。线程池用来管理多个线程,执行任务,避免频繁创建和销毁线程的开销。ThreadPoolExecutor是Java中常用的线程池实现类。 用户提到ctl参数,可能是一个内部使用的变量。我记得在查看ThreadPoolExecutor的源码时,确实有一个名为ctl的字段。ctl通常代表“control”,可能用来控制线程池的状态或者线程数量。 接下来,我需要了解ctl的具体结构。可能它被设计成一个原子整数(AtomicInteger),用来同时保存线程池的状态和有效线程数量。这样的话,可以通过位运算来高效地维护这两个信息,而不需要额外的同步措施。 线程池的状态可能有几种,比如RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。这些状态需要被管理,而同时线程池的工作线程数量也需要跟踪。将这两个信息合并到一个变量中,可以保证原子性操作,避免竞态条件。 比如,ctl的高位可能用来表示状态,低位表示线程数量。例如,使用32位的整数,前3位表示状态,后29位表示线程数。这样,通过位掩码和移位操作,可以快速获取或修改状态和线程数。 例如,RUNNING状态可能是最高三位为某个特定值,如111,而SHUTDOWN是000等等。线程数量的最大值则受限于后29位的范围,即大约5亿左右,这对于大多数应用足够了。 这样做的好处是,在处理线程池的状态和线程数变化时,可以原子性地更新这两个值,而无需额外的锁,提高了并发性能。例如,当线程池状态改变时,同时可能需要调整线程数,这时候一个CAS操作就能同时更新两者,确保一致性。 但是,为什么需要把两个变量合并成一个呢?可能是因为在并发环境下,同时修改状态和线程数需要保持一致性,单独维护两个变量可能需要更多的同步措施,而合并成一个原子变量可以简化操作,提高效率。 此外,ctl参数可能被多个内部方法使用,比如在提交任务、执行任务、关闭线程池等操作时,需要检查当前状态和线程数,来决定下一步的操作。例如,当提交新任务时,线程池需要判断当前是否处于运行状态,是否有空闲线程等,这些信息都可以通过解析ctl来快速获取。 总结一下,ctl参数在ThreadPoolExecutor中起到了关键的作用,通过一个原子变量同时维护线程池的状态和有效线程数量,利用位运算高效地进行操作,确保线程池的并发安全和性能。</think>在Java的`ThreadPoolExecutor`中,`ctl`参数是一个关键设计,用于**高效且原子地管理线程池状态和工作线程数量**。以下是逐步解析: --- ### **1. ctl的定义与结构** - **数据类型**:`ctl`是`AtomicInteger`类型,确保原子性操作。 - **二进制划分**: - **高3位**:表示线程池状态(如运行、关闭等)。 - **低29位**:表示有效的工作线程数量(最大支持约5亿个线程)。 --- ### **2. 线程池状态的编码** 线程池共有5种状态,通过高3位表示: - **RUNNING** (111):可接收新任务,处理队列中的任务。 - **SHUTDOWN** (000):不接收新任务,但处理队列中的任务。 - **STOP** (001):不接收新任务,不处理队列任务,中断进行中的任务。 - **TIDYING** (010):所有任务已终止,即将执行`terminated()`钩子。 - **TERMINATED** (011):`terminated()`执行完毕。 --- ### **3. 如何合并状态与线程数?** - **位运算规则**: ```java // 状态左移29位(占据高3位) int stateBits = state << 29; // 线程数直接取低29位 int workerCount = ctl & COUNT_BITS; // 合并状态和线程数 ctl = stateBits | workerCount; ``` - **示例**: - 若状态为`RUNNING`(111)且线程数为5,则二进制表示为: `111 00000000000000000000000000101` --- ### **4. ctl的作用** - **原子性保证**:通过单一变量操作,避免对状态和线程数分别加锁。 - **快速判断**:通过位掩码快速提取状态或线程数(例如判断线程池是否运行中)。 - **高效更新**:使用CAS(Compare-and-Swap)操作修改`ctl`,确保并发安全。 --- ### **5. 源码中的关键方法** - **获取状态**: ```java private static int runStateOf(int c) { return c & ~COUNT_BITS; // 掩码取高3位 } ``` - **获取线程数**: ```java private static int workerCountOf(int c) { return c & COUNT_BITS; // 掩码取低29位 } ``` - **更新状态和线程数**: ```java compareAndSetState(int expect, int update) // CAS操作更新ctl ``` --- ### **6. 实际应用场景** - **提交任务时**:检查状态是否为`RUNNING`,并决定是否创建新线程。 - **线程回收时**:减少工作线程数,若状态为`SHUTDOWN`且队列为空,则触发关闭流程。 --- ### **总结** `ctl`通过位压缩技术,将线程池状态和线程数合并到单一变量中,实现了**高效的无锁并发控制**。这种设计是Java线程池高性能的核心机制之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值