ThreadPoolExecutor

本文详细探讨了ThreadPoolExecutor的工作原理、参数设置,以及Executors提供的不同线程池类型及其应用场景。重点介绍了线程池的优化策略和RejectedExecutionHandler的拒绝策略。

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

ThreadPoolExecutor

池化思想:线程池、字符串常量池、数据库连接池

优点:

  • 提高资源利用率
  • 提高程序的响应速度
  • 便于统一管理线程对象
  • 可以控制最大的并发数

原理:

ThreadPoolExecutor (七大参数)

public ThreadPoolExecutor(int corePoolSize, //核心线程数
                              int maximumPoolSize,//非核心线程数,最大线程数
                              long keepAliveTime,//时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//阻塞队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler//拒绝策略
                         )

import java.util.concurrent.*;
public class Test {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(3, 5, 1L,
         										TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
         		 								Executors.defaultThreadFactory(),
         		 								new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 6; i++) {
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"===>办理业务");
            });
        }
        executorService.shutdown();
    }
}

Executors (三大方法)

newCachedThreadPool() :可以称作可缓存线程池,它的特点在于线程数是几乎可以无限增加的(实际最大可以达到 Integer.MAX_VALUE,为 2^31-1,这个数非常大,所以基本不可能达到),而当线程闲置时还可以对线程进行回收。也就是说该线程池的线程数量不是固定不变的,当然它也有一个用于存储提交任务的队列,但这个队列是 SynchronousQueue,队列的容量为0,实际不存储任何任务,它只负责对任务进行中转和传递,所以效率比较高。

newFixedThreadPool() :它的核心线程数和最大线程数是一样的,所以可以把它看作是固定线程数的线程池,它的特点是线程池中的线程数除了初始阶段需要从 0 开始增加外,之后的线程数量就是固定的,就算任务数超过线程数,线程池也不会再创建更多的线程来处理任务,而是会把超出线程处理能力的任务放到任务队列中进行等待。而且就算任务队列满了,到了本该继续增加线程数的时候,由于它的最大线程数和核心线程数是一样的,所以也无法再增加新的线程了。

newSingleThreadExecutor():它会使用唯一的线程去执行任务,原理和 FixedThreadPool 是一样的,只不过这里线程只有一个,如果线程在执行任务的过程中发生异常,线程池也会重新创建一个线程来执行后续的任务。这种线程池由于只有一个线程,所以非常适合用于所有任务都需要按被提交的顺序依次执行的场景,而前几种线程池不一定能够保障任务的执行顺序等于被提交的顺序,因为它们是多线程并行执行的。

//运行10000个任务
ExecutorService executorService1 = Executors.newCachedThreadPool(); // 0,max,60L,synchronousQueue 最快
// CPU 100% 
ExecutorService executorService2 = Executors.newFixedThreadPool(10); //10,10,0,LinkedBlockingQueue 慢
// 内存溢出
ExecutorService executorService3 = Executors.newSingleThreadExecutor(); // 1,1, 0,LinkedBlockingQueue 最慢
//
// 提交优先 执行优先 原则
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) { 
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

RejectedExecutionHandler(四大拒绝策略)

在使用线程池并且使用有界队列的时候,如果队列满了,任务添加到线程池的时候就会有问题,针对这些问题java线程池提供了以下几种策略:

  • AbortPolicy:这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。
  • DiscardPolicy:这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
  • DiscardOldestPolicy:如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。
  • CallerRunsPolicy :相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值