• JAVA线程池ThreadPoolExecutor的分析和使用(新手踩坑和推荐方案)


    一、Java中的ThreadPoolExecutor类

    java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
    在ThreadPoolExecutor类中提供了四个构造方法:

     1 public class ThreadPoolExecutor extends AbstractExecutorService {
     2     .....
     3     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
     4             BlockingQueue<Runnable> workQueue);
     5  
     6     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
     7             BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
     8  
     9     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
    10             BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
    11  
    12     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
    13         BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    14     ...
    15 }
    View Code

    从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
    下面解释下一下构造器中各个参数的含义:
    corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
    maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
    keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
    unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性。
    workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
        ArrayBlockingQueue  有界队列。当使用有限的maximumPoolSizes时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度的降低CPU使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞,则系统可能为超过您许可的更多线程安排时间,使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样可会降低吞吐量。
        LinkedBlockingQueue  无界队列。使用无界队列将导致在所有corePoolSize线程都忙时新任务在队列中等待。这样,创建的线程就不会超过corePoolSize(因此,maximumPoolSize的值也就无效了)。
        SynchronousQueue  直接提交。它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增加的可能性。

    threadFactory:线程工厂,主要用来创建线程;

    handler:表示当拒绝处理任务时的策略,有以下四种取值:

        ThreadPoolExecutor.AbortPolicy  默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。

        ThreadPoolExecutor.DiscardPolicy  新提交的任务被抛弃但不抛出异常

        ThreadPoolExecutor.DiscardOldestPolicy  丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

        ThreadPoolExecutor.CallerRunsPolicy  为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务。

    二、ThreadPoolExecutor的坑
        1)  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 6, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        一般人会以为如果请求的任务超过3个时,就会自动创建6个线程来执行,其实此线程池最多只会产生3个线程,为什么?
        当前线程数优先与corePoolSize 比较,大于corePoolSize ,则与workQueue容量(capacity)比较,但此处LinkedBlockingQueue默认构造器的capacity值为Integer.MAX_VALUE,你产生的任务数永远不会超过capacity值,那么新任务会一直加入workQueue,而不会创建新的线程,所以你会看到线程池里的线程数永远为3,而不是你期望的6。

    三、使用建议

        1) 服务器有超大内存,想节省CPU(线程池核心作务数为3,新任务无限加入workQueue,但是内存会一直暴涨,线程池中线程数永远控制在3个以节省CPU)
            this.threadPoolExecutor = new ThreadPoolExecutor(3, 6, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

        2) 服务器CPU资源丰富(线程池核心作务数为10,workQueue缓存的任务控制在20,超过20线程池就会创建新线程最多到100个,但是CPU使用率会暴涨。注:如果线程数量达到100,则新安排的任务根据策略CallerRunsPolicy则会在调用者线程中同步执行)
            this.threadPoolExecutor = new ThreadPoolExecutor(10, 100, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));
            this.threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        3) 均衡方案--推荐(线程池核心作务数为3,workQueue缓存的任务控制在20,超过20线程池就会创建新线程最多到6个,如果线程数量达到6,则新安排的任务根据策略CallerRunsPolicy则会在调用者线程中同步执行)
            this.threadPoolExecutor = new ThreadPoolExecutor(3, 6, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));
            this.threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

  • 相关阅读:
    H5分栏(第一章)
    数据库操作集合
    sql 存储过程
    数据库事务
    Sql 分页三种方式
    GridView 后台分页
    GridView 分页 上一页 下一页 跳转 前端分页
    GridView 分页
    web前端开发分享-css,js入门篇(转)
    Intellij IDEA,WebStorm-keymap(转)
  • 原文地址:https://www.cnblogs.com/mrhgw/p/14519872.html
Copyright © 2020-2023  润新知