线程池ThreadPoolExecutor的使用以及内部实现流程

前言

最近在业务中需要处理较多并且比较繁重的任务,于是自然就想到了线程池。于是乎,就毫不犹豫地用到ThreadPoolExecutor去自定义线程池,可没想到的是,因为自己的对ThreadPoolExecutor的无知,踩坑了,哎呦,我去(具体是什么坑下面介绍它的原理之后,再娓娓道来,嘻嘻)。

这篇文章主要内容包括两个方面:

  1. 简单使用
  2. 原理介绍

简单使用

ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

下面对各个参数进行介绍

  1. corePoolSize:线程池的核心线程数,也就是线程池中会始终保持至少有这么多线程,无论这些线程处于空闲或者工作状态。
  2. maximumPoolSize:线程池中的最大线程数。
  3. keepAliveTime:当线程数大于corePoolSize时,其他线程的最大空闲时间,当空闲时间大于这个数值之后,线程就会被销毁。
  4. unit:keepAliveTime的单位。
  5. workQueue:一个队列,用于存储执行前的task。
  6. threadFactory: 线程工厂。
  7. handler:当所有线程数为maximumPoolSize,且所有线程都处于工作状态时,并且workQueue容量已满时,对任务的拒绝策略。

上面的参数比较简单,这里对handler参数进一步进行说明,它支持的拒绝策略有四种:

// 直接丢弃,并抛出RejectedExecutionException异常
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
// 丢弃任务,不会抛异常
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
// 不丢弃当前job,换而丢弃workQueue中最老的job
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
// 不异步去执行job,当前线程直接同步去执行这个job
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
example

下面是一个简单的使用例子,从线程池的初始化,到执行有结果和无结果的异步任务,最后关闭线程池。

public static void main(String[] args) {
        // 申明一个线程工厂,创建线程的名称是test-thread-pool-加number
        ThreadFactory threadFactory = new ThreadFactory() {
            private final AtomicInteger threadNumber=new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "test-thread-pool-"+threadNumber.incrementAndGet());
            }
        };
        // 使用AbortPolicy拒绝策略
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        ExecutorService executorService = new ThreadPoolExecutor(1, 4, 1, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(5), threadFactory, handler);

        Callable callable = () -> {
            System.out.println(Thread.currentThread().getName() + " , I'm callable task");
            Thread.sleep(1000);
            return "task execute ok!";
        };
        try {
            // 提交一个有返回结果的异步任务
            String callableResult =  executorService.submit(callable).get();
            System.out.println(Thread.currentThread().getName()+ " " + callableResult);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 循环提交10个异步任务
        for (int i=0; i<10; ++i) {
            try {
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //关闭线程池
        executorService.shutdown();
    }

内部实现流程

其实内部实现,大家通过参数就可以得知,任务少的时候的线程数只有核心线程数,然后任务多的时候就会额外创建新的线程。但是,怎么去衡量任务多和任务少,具体什么时候任务会直接拿去执行、或者拿去放到队列里边。看到下面的流程图,真相可大白!

ThreadPoolExecutor 内部处理流程图

其中 workQueue就是存储执行前task的阻塞队列,workQueueSize即该队列的大小。当处理完job完之后的线程,它们还有一些生命周期。,对它们的处理流程如下:

线程池中的线程处理流程

总结来讲,在线程池内部主要分为了三个情况进行处理。

  1. workSize < corePoolSize时创建线程。
  2. workSize >= corePoolSize时,将job放到workQueue中。
  3. 当队列已经满了,则又创建新的线程去处理job,但是得满足当前线程数小于maximumPoolSize,否则进行reject处理。

踩坑经历以及总结

踩坑的原因呢,确实是因为对线程池的不了解。情况就是这么个情况。

我需要去处理一些任务比较耗时,且比较多的任务。于是就想到了线程池去处理,这也是没有问题的。我的参数是:

corePoolSize = 1, maximumPoolSize = 3, workSize  = 1000000

这样做错误在于,corePoolSize和maximumPoolSize都设置得过小,线程数根本不够用。当懂得原理之后,就更加的明白,workSize设置得如此之大,当job堆在满之前都是只有一个线程在战斗

所以,在平时使用中workSize不宜设置得过大,另外还有一个隐患就是workQueue中堆积了这么多job,若发生宕机,job会全部丢掉,这样其实是很不好的,可能发生丢数据或者其他情况。

此外,corePoolSize可以设置为主机核心数,maximumPoolSize设置为corePoolSize的几倍即可。

int corePoolSize = Runtime.getRuntime().availableProcessors()

本文就没有将ThreadPoolExecutor的源代码贴出来了,看图比看代码会直观一些,理解起来更容易。

嘿嘿嘿,就到这里了!

你可能感兴趣的:(线程池ThreadPoolExecutor的使用以及内部实现流程)