线程(十四)---ThreadPoolExecutor线程池

写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!   

   前面了解了Fork/Join线程池,再记录一下ThreadPoolExecutor线程池的使用,在使用前先说一下在《阿里巴巴java开发手册》中对并发处理的要求:1.线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程。2.线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这样的处理方式可以更加明确线程池的运行规则,避免资源耗尽的风险。

  那为什么要使用线程池呢?

1.通过重用已存在的线程,减少在创建和销毁线程上所消耗的时间以及系统资源的开销。
2.提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
3.方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。

 那为什么不允许使用Executors创建线程池呢?

因为使用Executors去创建线程池是有风险的,如下:

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

PS:之前文章中使用Executors创建的线程可以改为ThreadPoolExecutor方式。

 那线程池数量为多少合适呢?

  线程池数量:
  网上看到一个公式:Nthreads=Ncpu*Ucpu*(1+W/C)
  Nthreads=CPU数量,Ucpu=目标CPU的使用率,0<=Ucpu<=1,W/C=任务等待时间与任务计算时间的比率。

 说完这些,接下来只记录下ThreadPoolExecutor 线程池,先看下创建ThreadPoolExecutor 线程池的源码:

参数介绍:

1.int corePoolSize:核心线程数

   默认为0,当有任务来时就创建一个线程,当任务数量超过corePoolSize时会将任务放入任务队列,当任务队列也超了会继续创建新线程,直到线程数为maximumPoolSize。

2.int maximumPoolSize:线程池最大线程数

   当线程数>=corePoolSize,且任务队列已满时。如果还有任务添加到线程池,线程池会创建新线程来处理任务。
   当任务队列已满时,如果还有任务添加到线程池,线程池会执行reject操作。

3.long keepAliveTime:线程空闲时间

   默认情况,当线程数大于corePoolSize时,keepAliveTime才会生效,将空闲时长超过设定值的线程终止,直到线程数量等于corePoolSize,但是如果调用了allowCoreThreadTimeOut(boolean)方法并设置了参数为true,在线程池中的线程数不大于corePoolSize时, keepAliveTime参数也会起作用。

4.TimeUnit unit:参数keepAliveTime的时间单位.

5.BlockingQueue<Runnable> workQueue:任务队列

   它分为直接提交队列、有界任务队列、无界任务队列、优先任务队列;

   1)SynchronousQueue:直接提交队列,它没有容量,不存储任务的阻塞队列,每一个存入对应一个取出,串行化队列,当任务超出maximumPoolSize,线程池会执行reject操作.

   2)ArrayBlockingQueue:有界的任务队列,按FIFO的规则对任务进行存取,使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。

   3)无界的任务队列:LinkedBlockingQueue,按FIFO(先进先出)的规则存取任务,最大容量Integer.MAX_VALUE, 可能会将资源耗尽,使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

  4)PriorityBlockingQueue:有优先级的任务队列, PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

6.ThreadFactory threadFactory:线程工厂

   主要用来创建线程:默认值 DefaultThreadFactory。

7.RejectedExecutionHandler handler:表示当拒绝处理任务时的策略

    1)ThreadPoolExecutor.AbortPolicy:该策略会直接抛出异常(RejectedExecutionException异常),阻止系统正常工作。(默认handle)
    2)ThreadPoolExecutor.DiscardPolicy:该策略会默默丢弃无法处理的任务,不予任何处理,但是不抛出异常。
    3)ThreadPoolExecutor.DiscardOldestPolicy:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交。
    4)ThreadPoolExecutor.CallerRunsPolicy:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行。
    5)可自定义拒绝策略。

代码示例:

private static ExecutorService pool = new ThreadPoolExecutor(3,5,1000, TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>(),
       Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        for (int i=0;i<10;i++){
            pool.execute(new Task1(i));
        }
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

执行结果如下:

可以看到只有5个线程执行了,超出最大线程数后,执行了默认的拒绝策略。

将new SynchronousQueue<Runnable>()更换为new ArrayBlockingQueue<>(2)后,预期效果是:仍然5个线程执行,但是会执行7次,并执行拒绝策略报错,因为ArrayBlockingQueue队列中可以缓存2个任务,如果ArrayBlockingQueue初始化的队列长度大于等于5,所有任务会正常执行不再报错,ArrayBlockingQueue初始长度为2时执行结果如下:

ArrayBlockingQueue初始长度为大于等于5时执行结果如下:

更换为LinkedBlockingQueue后,预期效果是有3个线程执行,不会报错,因为多余的任务都放到LinkedBlockingQueue队列中,不再创建线程,执行效果如下:

更换为PriorityBlockingQueue有优先级的任务队列后,并不能保证优先级高的就一定先执行,只是相对来说优先级高的先执行的概率会大一些。

拓展:

1.线程池的关闭

调用线程池的shutdown()或shutdownNow()方法来关闭线程池。

shutdown:按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。如果已经关闭,则调用没有其他作用。
shutdownNow:尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。在从此方法返回的任务队列中排空(移除)这些任务。
中断采用interrupt方法,所以无法响应中断的任务可能永远无法终止。但调用上述的两个关闭之一,isShutdown()方法返回值为true,当所有任务都已关闭,表示线程池关闭完成,则isTerminated()方法返回值为true。当需要立刻中断所有的线程,不一定需要执行完任务,可直接调用shutdownNow()方法。

2.进行监控

利用线程池提供的参数进行监控,参数如下:

getTaskCount:返回曾计划执行的近似任务总数。因为在计算期间任务和线程的状态可能动态改变,所以返回值只是一个近似值。
getCompletedTaskCount:返回已完成执行的近似任务总数。因为在计算期间任务和线程的状态可能动态改变,所以返回值只是一个近似值,但是该值在整个连续调用过程中不会减少。
getLargestPoolSize:线程池曾经创建过的最大线程数量,通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。
getActiveCount:返回主动执行任务的近似线程数。
通过扩展线程池进行监控:继承线程池并重写线程池的beforeExecute(),afterExecute()和terminated()方法,可以在任务执行前、后和线程池关闭前自定义行为。如监控任务的平均执行时间,最大执行时间和最小执行时间等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值