线程池:ThreadPoolExector

本文详细介绍了Java中的线程池实现类ThreadPoolExecutor的继承关系及其重要接口ExecutorService的功能。探讨了execute与submit的区别,并讲解了线程池的七大参数和四种拒绝策略,包括AbortPolicy、DiscardPolicy、DiscardOldestPolicy和CallerRunsPolicy,以及它们在任务无法执行时的不同处理方式。

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

ThreadPoolExecutor继承关系

Java中的线程池有两个核心的类:ThreadPoolExector 和 ScheduledThreadPoolExecutor,以ThreadPoolExecutor为例,下图为ThreadPoolExecutor的继承关系。
在这里插入图片描述

ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。
ExecutorService接口增加了一些能力:(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;(2)提供了管控线程池的方法,比如停止线程池的运行。

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

Ps:execute 和 submit 区别

submit 有返回值,execute 没有返回值。 所以说可以根据任务有无返回值选择对应的方法。

   FutureTask<Integer> submit2 = (FutureTask<Integer>) pool.submit(new Callable<Integer>() {
                public Integer call() throws Exception {
                    return 1;
                }
            });
   System.out.println(submit2.get());

submit 方便异常的处理。 如果任务可能会抛出异常,而且希望外面的调用者能够感知这些异常,那么就需要

    Future future = pool.submit(new RunnableTest("Task2"));
        
        try {
            if(future.get()==null){//如果Future's get返回null,任务完成
                System.out.println("任务完成");
            }
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
            //否则我们可以看看任务失败的原因是什么
            System.out.println(e.getCause().getMessage());
        }

AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

线程池执行流程

线程池7大参数


int corePoolSize, // 核心线程池大小                         
int maximumPoolSize, // 线程池最大容量                      
long keepAliveTime, // 超时了没有人调用就会释放                          
TimeUnit unit, // 超时单位                         
BlockingQueue<Runnable> workQueue, // 阻塞队列                          
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动                          
RejectedExecutionHandler handle // 拒绝策略) 

线程池4种拒绝策略

第一种拒绝策略是 AbortPolicy,默认的拒绝策略,拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。

第二种拒绝策略是 DiscardPolicy,这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

第三种拒绝策略是 DiscardOldestPolicy,如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。

第四种拒绝策略是 CallerRunsPolicy,相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。

在这里插入图片描述

首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。

  • 当workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。

  • 当workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。

  • 当workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。

  • 当 workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

举例说明

ExecutorService pool = new ThreadPoolExecutor(2,
        3, 1,TimeUnit.MICROSECONDS,
        new ArrayBlockingQueue(4),
        Executors.defaultThreadFactory());
for(int i = 0; i < 100; i++){
    pool.execute(new Runnable() {
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    });

在这里插入图片描述

public class Test {
    private static ExecutorService pool;
    public static void main( String[] args )
    {
        //自定义线程工厂
        pool = new ThreadPoolExecutor(2, 4, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                new ThreadFactory() {
                    public Thread newThread(Runnable r) {
                        System.out.println("线程"+r.hashCode()+"创建");
                        //线程命名
                        Thread th = new Thread(r,"threadPool"+r.hashCode());
                        return th;
                    }
                }, new ThreadPoolExecutor.CallerRunsPolicy());

        for(int i=0;i<10;i++) {
            pool.execute(new ThreadTask());
        }
    }
}

class ThreadTask implements Runnable{
    public void run() {
        //输出执行线程的名称
        System.out.println("ThreadName:"+Thread.currentThread().getName());
    }
}

在这里插入图片描述

<think>嗯,我现在要理解JavaThreadPoolExecutor中的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、付费专栏及课程。

余额充值