常用线程池ThreadPoolExecutor

摘要:

      在之前的时候Java的线程既是工作单元,也是执行机制。但是从JDK1.5开始,就把工作单元和执行机制分开,工作单元包括Runnable和Callable,而执行机制就由Executor框架提供。

Executor框架的成员:

      ThreadPoolExecutorScheduledThreadPoolExecutorFuture接口、Runnable接口、Callable接口、Executors

这里在阿里编码规范里面提到过线程池使用的注意事项:

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

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

1)FixedThreadPool 和 SingleThreadPool:

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

2)CachedThreadPool 和 ScheduledThreadPool:

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

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

所以,我们需要显示的创建ThreadPoolExecutor,里面参数多,无所谓,搞懂就行,更好根据情况来设定自己需要的参数。

 

首先看一下构造函数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
//这里忽略那些赋值操作
}
  • corePoolSize:线程池中的核心线程数(在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来       才创建线程去执行任务)
  • maximumPoolSize:线程池中的最大线程数(最多只能创建这么多线程,如果还有任务过来一般直接放到阻塞队列里面去)
  • keepAliveTime:线程池中的线程存活时间(准确来说应该是没有任务执行时的回收时间,后面会分析)
  • unit:keepAliveTime的时间单位(在TimeUnit类中有7种静态属性)
  • workQueue:来不及执行的任务存放的阻塞队列(线程池的排队策略与BlockingQueue有关。有3种不同的策略,)
  • threadFactory:新建woker线程(注意不是我们提交的任务)是进行一些属性设置,比如线程名,优先级等等,有默认实现。
  • handler: 任务拒绝策略,当运行线程数已达到maximumPoolSize,队列也已经装满时会调用该参数拒绝任务,有默认实现。

运行策略:

1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

2、当调用 execute() 方法添加一个任务时,线程池会做如下判断:

    a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;

    b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。

    c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;

    d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。

3、当一个线程完成任务时,它会从队列中取下一个任务来执行。

4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行 的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

       这个过程说明,并不是先加入任务就一定会先执行。假设队列大小为 4,corePoolSize为2,maximumPoolSize为6,那么当加入15个任务时,执行的顺序类似这样:首先执行任务 1、2,然后任务3~6被放入队列。这时候队列满了,任务7、8、9、10 会被马上执行,而任务 11~15 则会抛出异常。最终顺序是:1、2、7、8、9、10、3、4、5、6。当然这个过程是针对指定大小的ArrayBlockingQueue<Runnable>来说,如果是LinkedBlockingQueue<Runnable>,因为该队列无大小限制,所以不存在上述问题。

去扒了一张图过来便于理解线程池中任务执行会经过哪些判断。

 

添加线程池任务的入口

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();//任务为空时抛出异常
    //如果线程池线程大小小于核心线程,就新建一个线程加入任务并启动线程
    //如果线程池线程大小大于核心线且且添加任务到线程失败,就把任务添加到阻塞队列
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {//新建线程并启动
        if (runState == RUNNING && workQueue.offer(command)) {//添加任务到队列
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);//添加到队列失败或已满,做拒接任务处理策略
        }
        //若阻塞队列失败或已满;这里新建一个线程并启动做应急处理(这里就是用到了maximumPoolSize参数)
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // 若线程池的线程超过了maximumPoolSize;就做拒绝处理任务策略
    }
}

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值