java多线程10:自定义线程池ThreadPoolExecutor

引言:在阿里新版619java开发手册有一段这样的话

1. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
2. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问
题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
3. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这
样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下: 1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

 自定义线程池的好处

1、重用线程池中的线程,避免因频繁创建和销毁线程造成的性能消耗。

2、更加有效的控制线程的最大并发数,防止线程过多抢占资源造成的系统阻塞。

3、对线程进行有效的管理。

既然好处这么多下面我们就来讲解一下自定义线程池ThreadPoolExecutor吧

ExecutorService exec = new ThreadPoolExecutor(20,
                            100,
                            60,
                            TimeUnit.SECONDS,
                            new ArrayBlockingQueue<Runnable>(4),
                            Executors.defaultThreadFactory(),
                            new ThreadPoolExecutor.CallerRunsPolicy());

一.参数详解

自上至下共7个参数

1.corePoolSize

核心线程数。在创建线程池之后,默认情况下线程池中并没有任何的线程,而是等待任务到来才创建线程去执行任务,当线程池中的线程数目达到 corePoolSize后,新来的任务将会被添加到缓存队列中,也就是那个workQueue,除非调用 ThreadPoolExecutor#prestartAllCoreThreads() 方法或者是 ThreadPoolExecutor # prestartCoreThread() 方法(从这两个方法的名字就可以看出是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或一个线程)。

PS:很多人不知道这个数该填多少合适,其实也不必特别纠结,根据实际情况填写就好,实在不知道,就按照阿里工程师的写法取下列值就好了:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

2.maximumPoolSize

线程池中的最大线程数。表示线程池中最多可以创建多少个线程,很多人以为它的作用是这样的:”当线程池中的任务数超过 corePoolSize 后,线程池会继续创建线程,直到线程池中的线程数小于maximumPoolSize“,其实这种理解是完全错误的。它真正的作用是:当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于 maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。另外超过 corePoolSize的线程被称做"Idle Thread", 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。

3.keepAliveTime

控制"idle Thread"线程的空闲存活时间。这个idle Thread就是上面提到的超过 corePoolSize 后新创建的那些线程,默认情况下,只有当线程池中的线程数大于corePoolSize,且这些"idle Thread"并没有被分配任务时,这个参数才会起作用。另外,如果调用了 ThreadPoolExecutor#allowCoreThreadTimeOut(boolean) 的方法,在线程池中的线程数不大于corePoolSize,且这些core Thread 也没有被分配任务时,keepAliveTime 参数也会起作用。

4.unit

参数keepAliveTime的时间单位,共7种取值,在TimeUtil中定义:

TimeUnit.DAYS;              //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

5.workQueue

阻塞队列。如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到该队列当中,注意只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池的状态决定如何处理该任务(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。

常用的阻塞队列有:

1)ArrayBlockingQueue       //基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue      //基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue        //这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

6.threadFactory

线程工厂。用来为线程池创建线程,当我们不指定线程工厂时,线程池内部会调用Executors.defaultThreadFactory()创建默认的线程工厂,其后续创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。

7.handler

拒绝执行策略。当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:         // 丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:       // 也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:    // 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:      // 由调用线程处理该任务

默认的4种并不十分友好,除了上面拒接策略我们还可以自定义拒绝策略

 ExecutorService pool = new ThreadPoolExecutor(// 自定义一个线程池

                1, // coreSize

                2, // maxSize

                60, // 60s

                TimeUnit.SECONDS, new ArrayBlockingQueue<>(3) // 有界队列,容量是3个

                , Executors.defaultThreadFactory()

                , new MyRejected()//自定义拒绝策略

        );

自定义MyRejectedl类 继承RejectedExecutionHandler 

public class MyRejected implements RejectedExecutionHandler {

 

    @Override

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        MyTask task = (MyTask) r;

        System.out.println("报警信息:"+task.getTaskName()+" 被线程池拒绝,没有被执行");

        //可以往消息队列中间件里面放 可以发Email等等

    }

}

二.ThreadPoolExecutor 的三种提交任务方式

executorService.execute(Runnable); //无返回值
executorService.submit(Runnable) ;//有返回值
inVokeAny(); 随机返回一个结果 ,inVokeAll();//返回所有结果

详见:https://www.cnblogs.com/ldq2016/p/8072563.html


三.线程池的关闭

当我们不需要使用线程池的时候,我们需要对其进行关闭...有两种方法可以关闭掉线程池...

i.shutdown()...

  shutdown并不是直接关闭线程池,而是不再接受新的任务...如果线程池内有任务,那么把这些任务执行完毕后,关闭线程池....

ii.shutdownNow()

  这个方法表示不再接受新的任务,并把任务队列中的任务直接移出掉,如果有正在执行的,尝试进行停止...

 

猜你喜欢

转载自blog.csdn.net/zhaofuqiangmycomm/article/details/113311879