【Executors和ThreadPoolExecutor】


前言

"Executors"和"ThreadPoolExecutor"都是Java中的并发工具类,都是用来创建和管理线程池的。

"Executors"是一个工厂类,它提供了创建线程池的静态方法,包括newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool等,这些方法可以创建不同类型的线程池。例如,newFixedThreadPool可以创建一个固定线程数的线程池,而newCachedThreadPool可以创建一个可以无限扩大的线程池。

然而,"Executors"的局限性在于它封装了创建线程池的方法,所以不能根据我们的使用情况来指定参数。如果需要更灵活的配置,可以使用"ThreadPoolExecutor"类。"ThreadPoolExecutor"提供了更多的参数,如核心线程数、最大线程数、空闲存活时间等,可以根据我们的需求来创建线程池。

总的来说,"Executors"使用方便,但灵活性较差,而"ThreadPoolExecutor"虽然使用相对复杂,但灵活性更高。


一、ThreadPoolExecutor

ThreadPoolExecutor 源码的一个构造方法

Executors 工厂类使用的就是该方法

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  1. corePoolSize:核心线程数,即线程池中始终保持运行的线程数量。

  2. maximumPoolSize:最大线程数,即线程池中允许同时运行的最大线程数量。

  3. keepAliveTime:空闲存活时间,即非核心线程在没有任务执行时,保持存活的最长时间。

  4. unit:空闲存活时间的单位,如秒、分钟等。

  5. workQueue:工作队列,用于存储等待执行的任务。

  6. defaultThreadFactory():默认线程工厂,用于创建线程。

  7. defaultHandler:默认拒绝策略,当线程池中的线程数达到最大线程数,并且工作队列已满时,线程池会拒绝新任务,此时会调用拒绝策略。

如何处理被拒绝的任务

四个官方的的拒绝策略工作方式
ThreadPoolExecutor.AbortPolicy()任务队列满后续的直接抛弃,并抛出异常RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString())
ThreadPoolExecutor.DiscardPolicy()任务队列满后续的直接抛弃,但是不抛异常
ThreadPoolExecutor.DiscardOldestPolicy()抛弃队头的任务,新任务队尾进,不抛异常
ThreadPoolExecutor.CallerRunsPolicy()线程池内的线程都在忙,那就让主线程来执行新任务,同时执行一个简单的反馈控制机制,降低新任务提交的速度

二、Executors.newCachedThreadPool

代码如下(示例):

public class Main {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        // newCachedThreadPool 内部实现如下(使用默认的 AbortPolicy)
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                0,// 核心线程数为 0
                Integer.MAX_VALUE,// 最大线程数为 0x7fffffff
                60L,// 
                TimeUnit.SECONDS,// 非核心线程存活时间为 60 秒
                new SynchronousQueue<Runnable>()// 工作队列
        );
        
    }
}

SynchronousQueue 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。(即必须进一个出一个,进去的不出去就卡住)

同时因为 SynchronousQueue 没有容量,而且maximumPoolSize(最大线程数)为Integer.MAX_VALUE,所以如果任务很多会不断地创建非核心线程,导致 OOM(内存溢出)

即任务过多会导致非核心线程不断创建,Executors.newCachedThreadPool出现OOM。


三、Executors.newFixedThreadPool

代码如下(示例):

public class Main {
    public static void main(String[] args) {
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

		// newFixedThreadPool 内部实现如下(使用默认的 AbortPolicy)
        ThreadPoolExecutor executor1 = new ThreadPoolExecutor(
                5,// 核心线程数为 5
                5,// 最大线程数为 5
                0L,//没有非核心线程数
                TimeUnit.MILLISECONDS,//
                new LinkedBlockingQueue<Runnable>()
        );

    }
}

LinkedBlockingQueue 如果不指定容量,默认的容量是 Integer.MAX_VALUE,如果设置的核心线程过少,任务又很多,线程都在忙碌,而此时默认的拒绝策略是 AbortPolicy,任务就会不断地添加到该阻塞队列,最终导致OOM。

即任务过多线程太少会导致工作队列不断变大, Executors.newFixedThreadPool 出现OOM。


四、Executors.newSingleThreadExecutor

代码如下(示例):

public class Main {
    public static void main(String[] args) {
		// 等同于 Executors.newFixedThreadPool(1)
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

		// Executors.newSingleThreadExecutor 内部实现如下(使用默认的 AbortPolicy)
        ThreadPoolExecutor executor2 = new ThreadPoolExecutor(
                1,// 核心线程数为 1
                1,// 最大线程数为 1
                0L,// 没有非核心线程
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()
        );

    }
}

见名知意,该方法仅仅创建一个线程,工作队列为 LinkedBlockingQueue,拒绝策略为AbortPolicy,所以会和Executors.newFixedThreadPool 出现相同的问题。

同理,即任务过多线程太少会导致工作队列不断变大,Executors.newSingleThreadExecutor 出现OOM。

  • 30
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值