线程池ThreadPoolExecutor详解,Executor,拒绝策略对应使用规则源码解析

本文详细介绍了Java中的线程池ThreadPoolExecutor的参数、核心概念如核心线程数、最大线程数、存活时间、阻塞队列等。还讨论了四种拒绝策略:AbortPolicy、DiscardPolicy、DiscardOldestPolicy和CallerRunsPolicy,并通过示例展示了它们的行为。此外,文章提到了Executors工厂类创建线程池的方法,如newFixedThreadPool、newCachedThreadPool和newSingleThreadPool,并强调了线程池关闭的重要性。

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

线程池

Executor

执行提交的可运行(Runnable)任务的对象。此接口提供了一种方式将任务提交与每个任务的执行机制进行解耦,包括线程使用、调度等的详细信息。通常使用执行器而不是显式地创建线程。例如,与其为一组任务调用new Thread(new(RunnableTask()).start(),不如使用:

   /*
   只需要将要执行的任务作为参数提交过来 就可以执行 具体执行细节是黑盒操作
   传统线程执行者和提交者都是由同一操作者执行 线程池则将前者交给程序完成
   
	线程池理念类似消息队列 
	我们提交者new RunnableTask1() 相当于MQ中的生产者 线程池 work执行类似于消费者
	
   */
   Executor executor = anExecutor;

   executor.execute(new RunnableTask1());
   executor.execute(new RunnableTask2());
   ...

但是,Executor接口并不严格要求执行是异步的。在最简单的情况下,执行器可以立即在调用方线程中运行提交的任务:

在这里插入图片描述
在这里插入图片描述

ThreadPoolExecutor

在这里插入图片描述

   /**
     *使用给定的初始参数创建新的ThreadPoolExecutor。
     *
     *corePoolSize – 池中要保留的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
     				意思最少要保留的线程数 哪怕没有任务执行 也不会被销毁
     				
     * maximumPoolSize – 池中允许的最大线程数
     * 
     * keepAliveTime – 当线程数大于核心时,这是多余空闲线程在终止前等待新任务的最长时间。
     * 		如corePoolSize =5 maximumPoolSize= 10 
     * 		当前线程数为8 此时 8个线程无任务 当时间超过keepAliveTime多余3个就会被销毁
     * 
     * unit–keepAliveTime参数的时间单位
     * 
     * @param workQueue 工作队列在任务执行之前用于保留任务的队列。
     * 					此队列将只保存由execute方法提交的可运行runnable任务。
     * 
     * threadFactory – 执行器创建新线程时使用的工厂
     * 
     * handler–当执行因达到线程边界和队列容量而被阻止时使用的处理程序(拒绝策略) 
     * 					意思就是当任务大于线程执行数 多余的任务该如何处理

     */
  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

后三个参数我们可以使用自定义的类去应用我们生产环境

线程池参数

int corePoolSize 核心线程数

线程池当中所一直维护的线程数量,如果线程池处于任务空闲期间,那么该线程也不会被回收掉(注意如果线程池数量小于corePoolSize值 执行多个任务会先创建线程执行任务超过该值才会进行复用 设值讲究 一般是cpu核心数X2附近

int maximumPoolSize 最大线程数

线程池中所维护的线程数的最大数量

long keepAliveTime

超过了corePoolSize的线程在经过keepAliveTime时间后如果一直处于空闲状态, 那么超过的这部分线程将会被回收掉

BlockingQueue workQueue 阻塞队列(队列的根接口)

指的向线程池所提交的任务位于的阻塞队列,它的实现有多种方式

SynchronousQueue同步队列

单位中有且只含有一个任务 当前任务被弹出下个任务才能进入 队列存在任务,新来任务时拒绝策略就会生效

ThreadFactory threadFactory 线程工厂

线程池的ThreadFactory必须实现ThreadFactory 同时重写newThread方法
在这里插入图片描述

Executors.DefaultThreadFactory实现了该工厂并且重写了newThread方法
线程工厂,用于创建新的线程并被线程池所管理,默认线程工厂所创建的线程都是用户线程且优先级为正常优先级(5)threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n
为线程池内的线程编号)。

Executors.DefaultThreadFactory 线程池默认使用的线程工厂

在这里插入图片描述
由上图可以知晓线程池的线程都是用户线程,我们在使用完线程池需要手动关闭线程,并且优先级默认设值都为5

RejectedExecutionHandler handler 拒绝执行处理器(4种拒绝策略根接口)

拒绝执行处理器(拒绝策略) 表示当前线程池中的线程都在忙于执行任务且阻塞队列也已经满了的情况下,新到来的任务该如被对待和处理 ,JDK提供了四种拒绝策略

RejectedExecutionHandler 拒绝执行处理程序(策略根接口)

无法由ThreadPoolExecutor执行的任务的处理程序。(意思就是提供线程池无法执行的任务对应的处理策略的模板)

一般JDK提供的策略不会满足我们日常开发,里面对应方法rejectedExecution 具体策略方法实现我们根据自己的实际业务情况进行重写

/**
 * A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}.
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface RejectedExecutionHandler {

    /**
   方法,当execute无法接受任务时,ThreadPoolExecutor可能会调用该方法。
   当线程或队列槽因超出其界限而不再可用时,或者在执行器shutdown关闭时,可能会发生这种情况。
     *
     * @param r–请求执行的可运行任务
     * @param executor–尝试执行此任务的执行者(线程池对象)
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

该接口就是当线程池队列满了时新来的任务处理手段的概括,具体策略由子类进行处理,它的实现类有四种,都是ThreadPoolExecutor类当中的内部类
在这里插入图片描述

四种拒绝策略
1.AbortPolicy(终止策略)

这是一个处理器针对被拒绝的任务 直接抛出RejectedExecutionException异常(任务无法执行抛出异常)

    /**
     *这是一个处理器针对被拒绝的任务 直接抛出RejectedExecutionException异常
     * 
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * 构造方法
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         * 总是抛出RejectedExecutionException
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
2.DiscardPolicy(丢弃策略)

如下图我们可以看到rejectedExecution执行拒绝的策略为空实现 什么都不做,也不抛出异常


    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
3.DiscardOldestPolicy(丢弃旧任务策略)

一种被拒绝任务的处理程序,丢弃(workQueue队列)最老的未被处理请求(队列最先被放进去的任务),然后调用e.execute(r)重试执行当前任务(注意依然要走流程),除非执行器关闭,在这种情况下任务被丢弃。


    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
	       获取并忽略执行器将执行的下一个任务(如果有一个任务立即可用),
	       然后重试执行任务r,除非执行器关闭,在这种情况下,任务r被丢弃。
         *@param r请求执行的可运行任务
         *@param e试图执行此任务的执行者(线程池)
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {//线程池是否被关闭
            	//弹出WorkQueue队列第一个元素 
                e.getQueue().poll();
                //将任务插到队列队尾 依然要走完整流程
                e.execute(r);
            }
        }
    }
4.CallerRunsPolicy(调用方运行策略)

被拒绝任务的处理程序,它直接由提交任务的线程来运行这个提交的任务,除非executor已关闭,在这种情况下任务被丢弃。

 /**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
            	//直接执行
                r.run();
            }
        }
    }
内置拒绝实例使用
public class MyTest2 {

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());


        IntStream.range(0, 9).forEach(x -> threadPoolExecutor.submit(()->{
            //每个线程睡1S
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            IntStream.range(0, 2).forEach(i -> System.out.println(Thread.currentThread().getName()));
        }));
        /*
            线程池创建的线程 只是用户线程不是一个守护线程 对于JVM来说 主线程执行完成 并且没有任何子线程正在执行
            这时JVM进程就会退出(守护线程存在也会退出) 所以主线程mian就不会退出

            我们在执行完任务要结束线程池线程  如shutdown shutdownNow
         */
        threadPoolExecutor.shutdown();

    }
}

当我们使用CallerRunsPolicy

DiscardPolicy策略:

会抛出如下java.util.concurrent.RejectedExecutionException 拒绝执行异常 [Running, pool size = 5, 活动线程数 = 5, 排队任务 = 3, 已完成任务 = 0]

当我们创建8个以下任务时,线程至多出现8个,当我们创建第9个任务此时最大线程数为5 阻塞队列最多为3个任务
 此时 5+3 = 8-0 < 9 此时第9个任务一提交 超出最大任务数 就会执行拒绝策略AbortPolicy


Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1f89ab83 rejected from java.util.concurrent.ThreadPoolExecutor@e73f9ac[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
	at com.example.demo.com.concurrecy.concurrency9.MyTest2.lambda$main$2(MyTest2.java:15)
	at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
	at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:557)
	at com.example.demo.com.concurrecy.concurrency9.MyTest2.main(MyTest2.java:15)

DiscardPolicy策略:

当使用DiscardPolicy 当任务大于8此时不会抛出异常 但是任务执行数量会减少 本例 2X9 = 18 而实际打印为 8x2 = 16有两个任务被丢弃 但是不会告知

DiscardOldestPolicy策略:

同上 主要是移除队列最老的任务

callerRunsPolicy策略:

 此时任务不会丢弃,而是由调用方执行多余任务

    pool-1-thread-5
    pool-1-thread-5
    pool-1-thread-1
    pool-1-thread-1
    main
    main
    pool-1-thread-3
    pool-1-thread-3
    pool-1-thread-4
    pool-1-thread-4
    pool-1-thread-2
    pool-1-thread-2
    pool-1-thread-5
    pool-1-thread-5
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-3
    pool-1-thread-3

Executors示例

public class MyTest1 {

    public static void main(String[] args) {
        /*
         Executors 工厂类 提供多种方法创建各种类型线程池
         (最好手动创建线程更灵活的使用线程池参数 Executors将其隐藏 实际业务我们需要根据并发量来调节线程池参数 如默认采用拒绝策略都是AbortPolicy 直接抛出异常)

         如: newFixedThreadPool(3) 固定大小为3的线程池
            newSingleThreadExecutor 单例线程池
            newCachedThreadPool 缓冲线程池 pool会根据任务数自动分配线程执行任务 具体线程数根据任务决定

         本质上底层都是实例化ThreadPoolExecutor 根据不同的线程池 传入不同的对象
         */
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        IntStream.range(0, 10).forEach(x -> {
            executorService.submit(() -> IntStream.range(0, 50).forEach(i -> System.out.println(Thread.currentThread().getName())));

        });
        /*
            线程池创建的线程 只是用户线程不是一个守护线程 对于JVM来说 主线程执行完成 并且没有任何子线程正在执行
            这时JVM进程就会退出(守护线程存在也会退出) 所以主线程mian就不会退出

            我们在执行完任务要结束线程池线程  如shutdown shutdownNow
         */
        executorService.shutdown();
    }
}

不同线程池 使用的线程数数不同的

/*
Executors.newFixedThreadPool(3); 只有三个线程执行任务

...
pool-1-thread-2
...

pool-1-thread-1
...
pool-1-thread-3
...


 */
/*
Executors.newSingleThreadExecutor(); 只有一个线程执行任务
....
pool-1-thread-1
....
 */

/*
Executors.newCachedThreadPool(); 线程池根据任务分配多个线程执行任务
....
pool-1-thread-1
ool-1-thread-8
pool-1-thread-2
pool-1-thread-2
pool-1-thread-8
pool-1-thread-3
....
pool-1-thread-8
pool-1-thread-9
pool-1-thread-8
....
 */

对于线程池来说
Executors相当于工厂类 提供多种方法创建各种类型线程池, (但是 最好手动创建线程更灵活的使用线程池参数 Executors将其隐藏 实际业务我们需要根据并发量来调节线程池参数 如默认采用拒绝策略都是AbortPolicy 直接抛出异常)

注意事项

线程池创建的线程 只是用户线程不是一个守护线程 对于JVM来说 主线程执行完成 并且没有任何子线程正在执行这时JVM进程就会退出(守护线程存在也会退出) 所以主线程mian就不会退出我们在执行完任务要结束线程池线程 如shutdown shutdownNow

Executors不同类型线程池

Executors.newFixedThreadPool() 固定大小线程池

我们可以查看Executors.newFixedThreadPool(3);具体代码实现 其他的线程池类似
在这里插入图片描述

每个线程池都对应一个重载的线程工厂可以不使用默认的Executors.defaultThreadFactory() 传入自定义的ThreadFactory工厂
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
默认的的线程工厂defaultThreadFactory 这个可以使用自定的
在这里插入图片描述
默认的拒绝策略AbortPolicy终止策略。
在这里插入图片描述

创建了一个核心线程数corePoolSize为3 MaximumPoolSize为3的线程keepAliveTime为0无界的队列 默认使用的策略是终止策略直接抛出异常

Executors.newCachedThreadPool() 缓存线程池

在这里插入图片描述
两种重载
在这里插入图片描述
我们看出CachedThreadPool对应的核心线程数corePoolSize=0,最大线程数maximumPoolSize为Integer.MAX_VALUE线程空闲keepAliveTime=60(缓存60S) 就会销毁线程池中的线程 采用的队列SyncronousQueue阻塞队列 说明阻塞队列只能有且一个任务 默认的拒绝策略为abortPolicy

Executors.newSingleThreadPool() 单例线程池

在这里插入图片描述
我们看出CachedThreadPool对应的核心线程数corePoolSize=1,最大线程数maximumPoolSize为1 无缓存时间 就会销毁线程池中的线程 采用的队列LinkedBlockingQueue阻塞队列 说明阻塞队列是无界(默认)的 默认的拒绝策略为abortPolicy

SmartThreadPool是大名鼎鼎的.Net线程池项目,基于.Net开发,比.Net内置的线程池更胜一筹。1、为什么需要使用线程池(Thread Pool)减少线程间上下文切换。线程执行一定的时间片后,系统会自动把cpu切换给另一个线程使用,这时还需要保存当 前的线程上下文状态,并加载新线程的上下文状态。当程序中有大量的线程时,每个线程分得的时间片会越来越少,可能会出现线程未处理多少操作,就需要切换到 另一线程,这样频繁的线程间上下文切换会花费大量的cpu时间。减少内存占用。系统每创建一条物理线程,需要大概花费1MB的内存空间,许多程序喜欢先创建多条物理线程,并 周期轮询来处理各自的任务,这样既消耗了线程上下文切换的时间,还浪费了内存。这些任务可能只需要一条线程就能满足要求。假如某一任务需要执行较长的周 期,线程池还可以自动增加线程,并在空闲时,销毁线程,释放占用的内存。2、为什么不使用.Net默认的线程池.Net默认的线程池(ThreadPool)是一个静态类,所以是没办法自己创建一个新的程序池的。默认的线程池与应用程序域 (AppDomain)挂钩,一个AppDomain只有一个线程池。假如在线程池中执行了一个周期较长的任务,一直占用着其中一个线程,可能就会影响到 应用程序域中的其他程序的性能。例如,假如在Asp.Net的线程池中执行一个周期较长的任务,就会影响请求的并发处理能力(线程池默认有个最大线程 数)。 3、SmartThreadPool特性和优点    SmartThreadPool特性如下:可创建线程池实例。可动态调整线程池工作线程数量。WorkItem 可以返回信息。未执行 WorkItem 可被取消。WorkItem 执行时可使用调用者上下文。调用者可等待多个或全部 WorkItem 执行结束。WorkItem 允许拥有一个执行结束时被执行的 PostExecute 回调委托。可以向 WorkItem 传递一个状态对象,并且会在执行结束时自动调用 IDisposable.Dispose()。WorkItem 异常会传递给调用者。支持 WorkItem 分组。可挂起线程池或分组。可以设置 WorkItem 优先级。可以设置线程优先级。4、使用示例 最简单的使用方法:// 创建一个线程池 SmartThreadPool smartThreadPool = new SmartThreadPool();    // 执行任务 smartThreadPool.QueueWorkItem(() => {      Console.WriteLine("Hello World!"); });带返回值的任务:// 创建一个线程池 SmartThreadPool smartThreadPool = new SmartThreadPool();   // 执行任务 var result = smartThreadPool.QueueWorkItem(() => {     var sum = 0;     for (var i = 0; i  {     //模拟计算较长时间     Thread.Sleep(5000);       return 3; });   var result2 = smartThreadPool.QueueWorkItem(() => {     //模拟计算较长时间     Thread.Sleep(3000);       return 5; });   bool success = SmartThreadPool.WaitAll(     new IWorkItemResult[] { result1, result2 });   if (success) {     // 输出结果     Console.WriteLine(result1.Result);     Console.WriteLine(result2.Result); }5、结论 使用SmartThreadPool可以简单就实现支持多线程的程序,由线程池来管理线程,可以减少死锁的出现。SmartThreadPool还支持简单的生产者-消费者模式,当不需要对任务进行持久化时,还是很好用的。 6、扩展阅读 http://www.codeproject.com/KB/threads/smartthreadpool.aspx http://smartthreadpool.codeplex.com/http://www.albahari.com/threading/ 标签:线程池
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值