ThreadPoolExecutor简析

为什么使用线程池

使用线程池的好处是减少在创建和销毁线程上所花的时间和系统资源的开销,节省资源,提高效率。如果不使用线程池,有可能会造成系统创建大量同类线程而导致内存耗尽或者“过度切换”的问题。所以线程资源最好由线程池提供,不要在应用中自行显示创建线程。

Executor

Executor是java中线程池的核心接口,只有一个方法execute(Runnable command)

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

类关系说明

ThreadPoolExecutor是Executor接口的主要实现类

类关系
类关系

ExecutorService是Executor的子接口,增加了一些对线程池的控制方法,关闭线程池,批量调用等
AbstractExecutorService是一个抽象类,对ExecutorService的方法提供了默认实现,ThreadPoolExecutor继承此类

ThreadPoolExecutor

构造方法

ThreadPoolExecutor是线程池的真正实现类,可以通过构造方法的各种参数组合来配置不同的线程池。构造方法有以下四种

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

先来解释一下各个参数:

  • int corePoolSize

    该线程池中核心线程数。线程池在新建线程的时候,如果当前活动线程总数小于核心线程数,则会新建核心线程,直到达到corePoolSize,如果超过核心线程数,则会创建临时线程,也就是非核心线程,两者的区别在于非核心线程在执行完任务后超过规定时长会被销毁,而核心线程执行完任务会一直存在,不论是否闲置。

  • int maximumPoolSize

    该线程池中线程总数的最大值。线程总数=核心线程数+非核心线程数

  • long keepAliveTime

    该线程池中非核心线程的存活时间。一个非核心线程,如果处于闲置状态,当闲置时长超过这个参数设置的时间,则会被销毁,如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么这个参数会同样作用于核心线程

  • TimeUnit unit

    keepAliveTime的单位。TimeUnit是一个枚举类型,其包括:

    NANOSECONDS : 微毫秒 1微毫秒 = 1微秒 / 1000</br>
    MICROSECONDS : 微秒 1微秒 = 1毫秒 / 1000</br>
    MILLISECONDS : 毫秒 1毫秒 = 1秒 /1000</br>
    SECONDS : 秒</br>
    MINUTES : 分</br>
    HOURS : 小时</br>
    DAYS : 天</br>

  • BlockingQueue<Runnable> workQueue

    该线程池中的任务队列。维护着所有等待执行的Runnable对象,常用的workQueue有:

    1. SynchronousQueue:同步队列,一个没有容量大小的阻塞队列,将任务同步交付给工作线程,也就是说当这个队列接收到任务的时候,会直接提交给线程进行处理,如果核心线程都已经在工作了,则会创建非核心线程,直到线程数达到maximumPoolSize,从而执行拒绝策略
    2. LinkedBlockingQueue:构造函数不传容量大小会默认为Integer.MAX_VALUE,一般都使用默认值,所以当这个队列接收到任务时,如果当前线程数小于核心线程数,则会创建核心线程,否则会进入队列等待,又因为队列默认大小是Integer.MAX_VALUE,即所有超过核心线程数的任务量都将被添加到队列中去,从而导致maximumPoolSize参数失效,因为这个队列几乎不可能满
    3. ArrayBlockingQueue:必须要限定容量大小的队列,当接收到任务时,如果没有达到核心线程数,则新建核心线程处理任务,如果核心线程已满,则进入队列等待,如果队列也满了,则会创建非核心线程处理任务,如果总线程数达到了maximumPoolSize,则会执行拒绝策略
    4. PriorityBlockingQueue:无界的阻塞队列,类似于LinkedBlockingQueue,但其所含对象的顺序并不是FIFO,而是根据对象的自然排序顺序或者构造函数的Comparator决定的顺序
  • ThreadFactory threadFactory:创建线程的方式。一般不用,默认实现即可

  • RejectedExecutionHandler handler:拒绝策略。当线程数达到最大值且队列已满时会执行拒绝策略,ThreadPoolExecutor自己提供了四个拒绝策略:

    1. AbortPolicy:默认策略,直接放弃任务并抛出RejectedExecutionException异常
    2. CallerRunsPolicy:用调用者的线程执行任务
    3. DiscardOldestPolicy:抛弃队列中最老的那个任务,也就是排队时间最长的那个任务
    4. DiscardPolicy:抛弃当前将要加入队列的任务

主要方法

  • execute:开始执行一个任务
  • shutdown:启动之前提交的任务会执行完并有序关闭,不接受新任务,如果已经关闭,调用没有任何效果
  • shutdownNow:试图立即停止所有积极执行的任务,停止处理等待的任务,并返回未执行的任务列表
  • remove:取消在队列中等待还未执行的任务,已执行的无法取消

当然还有一系列的getXxx方法,方便对线程池的监控

getXxx方法
getXxx方法

总结

引用《阿里巴巴Java开发手册》中的一段话来总结一下

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

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

1)FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool 和 ScheduledThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

一个线程池在应用中如果不再被引用且没有剩余的线程时,这个线程池会被自动的shutdown。因此如果你想在忘记执行shutdown方法时也能正常的关闭线程池,建议设置一个有限的keepAliveTime,同时也执行下 allowCoreThreadTimeOut(true)

enjoy conding...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。