当前位置 博文首页 > 立志欲坚不欲锐,成功在久不在速度:并发编程-ThreadPoolExecuto

    立志欲坚不欲锐,成功在久不在速度:并发编程-ThreadPoolExecuto

    作者:[db:作者] 时间:2021-07-16 13:16

    目录

    前言:

    FixedThreadPool

    参数:

    执行流程图:

    ?代码测试:

    执行流程

    不推荐使用该方式来创建线程池:

    SingleThreadExecutor

    参数:

    流程图:

    CachedThreadPool

    SynchronousQueue队列的特点:

    执行流程图:

    项目中用了这个Cahce线程池出现的问题:

    Executors 返回线程池对象的弊端如下:


    前言:

    上一篇介绍的是直接使用ThreadPoolExecutor来创建线程池,很多人也称通过ThreadPoolExecutor创建线程池为自定义线程池

    Executor其实还提供了三种类型的线程池:

    FixedThreadPool

    先来看一下FixThreadPool的构造函数:?

    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    
    
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>(),
                                          threadFactory);
        }

    会发现底层其实用的就是ThreadPoolEexcutor来实现的,只不过是参数已经写死了

    参数:

    核心线程数: nThreads 外面传递进来

    最大线程数: 等于核心线程数

    时间: 0

    时间单位: 毫秒

    队列: new LinkedBlockingQueue<Runnable> 无限大

    public LinkedBlockingQueue() {
            this(Integer.MAX_VALUE);
        }

    执行流程图:

    ?

    我们从参数上可以发现它的核心线程数就等于最大线程数

    ?代码测试:

    执行流程

    • 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务;
    • 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 LinkedBlockingQueue
    • 线程池中的线程执行完 手头的任务后,会在循环中反复从 LinkedBlockingQueue 中获取任务来执行; 通过take拿

    不推荐使用该方式来创建线程池:

    ????????由于核心线程数就等于最大线程数,并且阻塞队列是无限增大的,所以当任务过来的时候他会无限的排队所以不推荐使用这种方式,因为会造成OOM。

    ????????使用无界队列?LinkedBlockingQueue?作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。无界队列作为线程池的工作队列会对线程池带来不好的影响,说简单点就是可能会导致 OOM,队列的本质是数据结构,就是用来存储的,所以队列满了才会出现内存溢出

    SingleThreadExecutor

    SingleThreadExecutor?是只有一个线程的线程池。下面看看SingleThreadExecutor 的实现:

    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }

    参数:

    核心线程数:1

    最大线程数:1

    线程队列: 和fixed的一样都是无限大

    流程图:

    ?

    • 如果当前运行的线程数少于 corePoolSize,则创建一个新的线程执行任务;
    • 当前线程池中有一个运行的线程后,将任务加入 LinkedBlockingQueue
    • 线程执行完当前的任务后,会在循环中反复从LinkedBlockingQueue 中获取任务来执行;

    ????????同样也不推荐使用sing线程池,因为他和fix线程池一样都是使用无界队列作为线程池队列,所以会造成OOM,sing线程池适合的业务场景就是需要按照顺序来依次执行,但是效率会很低

    CachedThreadPool

    是一个会根据需要不断创建新线程的线程池

    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>(),
                                          threadFactory);
        }

    ?????????CacheThreadPool的corePoolSize被设置为0,但是maximumPoolSize被设置为最大值,几乎是无界的,也就意味着如果主线程提交任务的速度高于线程处理任务的速度,CachedThreadPool就会不断的创建新的任务,但是这个线程池的阻塞队列用的是SynchronousQueue

    SynchronousQueue队列的特点:

    队列中同时只有一个节点,只能装一个数据,只有当前的数据被消费了才能继续装新的数据 (一夫一妻制)传球手,这个特点不会导致Cache线程池先出OOM,但是由于它的最大线程数为无限大会出现CPU爆满的问题

    执行流程图:

    ????????从图上我们可以发现,几乎所有的任务都创建线程了,因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程;每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务

    项目中用了这个Cahce线程池出现的问题:

    ?所以还是要谨慎消息使用这个线程,在项目中我们在汇总成绩部分用到了线程池,但是由于同事使用了这个Cache线程池来执行了批量导出的任务,导致任务多的时候CPU打满了,后来通过线上排查发现所有的线程都在执行这个任务,导致其他工作无法正常进行,后来将这部分优化为使用threadPoolExecutor来执行线程任务,性能提升了很多,不会出现CPU打满的情况

    Executors 返回线程池对象的弊端如下:

    • FixedThreadPool?和?SingleThreadExecutor?: 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
    • CachedThreadPool 和 ScheduledThreadPool?: 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 CPU打满。

    cs