ThreadPoolExecutor 官方使用说明

Posted Shen_JC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadPoolExecutor 官方使用说明相关的知识,希望对你有一定的参考价值。

介绍ThreadPoolExecutor之前首先要了解一下Executor是个什么东西?

Executor接口

Executor是用来执行Runnable的task。这个接口提供了一种解耦关于“任务的提交与任务的执行”,包含了线程的使用和调节等等。一个Executor通常用于替换显示申明线程。

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 <tt>Executor</tt> 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

一个ExecutorService通过一个线程中来执行每个提交的task,通常使用Executors 工厂方法来配置。

线程池带来两个不同的问题:他们通常在执行大量的异步任务时候可以改进性能,由于减少每个任务调用的开销,他们提供了一种管理限制和管理资源的途径,包括线程,在执行大量任务时的消耗。每个ThreadPoolExecutor还保持一些基本的统计数据,例如完成任务的数量。

在使用过程中,这个类提供了很多可配置的参数和可扩展的hooks。可是,Executors提供了几个常用的ThreadPoolExecutor:newCachedThreadPool()(没有边界的线程池,动态的线程回收),newFixedThreadPool(int) (固定大小的线程池)和newSingleThreadExecutor() (一个后台线程),可以通过调用工厂方法来直接使用,官方更建议这样使用。要不然就要使用下列指南当手动配置这个类:

构造函数说明

ThreadPoolExecutor有好几个构造函数,最终都是调用下面这个。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

构造函数中参数说明:
corePoolSize: 线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
threadFactory:创建执行线程的工厂
handler: 线程池对拒绝任务的处理策略

corePoolSize 和 maximumPoolSize

 ThreadPoolExecutor会动态的调整线程池的大小 (see getPoolSize()) 根据设置的“corePoolSize ”和“maximumPoolSize ”。当一个新的任务通过execute(Runnable)被提交并且 runing的线程数少于“corePoolSize”,那么一个新的线程会被创建来控制这个请求,即使当时有空闲的worker 线程。当corePoolSize < 当前running worker线程 < maximumPoolSize 时,只有当task queue为空的时候会创建一个新的线程。通过设置corePoolSize and maximumPoolSize 相同, 可以得到一个大小固定的线程池。通过设置maximumPoolSize 为无限大的数字比如Integer.MAX_VALUE,可以的得到一个任意并发的线程池。典型的, core and maximum pool sizes通过构造来设置,当然也可以通过 setCorePoolSize(int) and setMaximumPoolSize(int)来修改。

灵活的构造函数

 默认情况下,只有当新的tasks到达的时候,core threads才会被初始化创建和开始。但是这个可以被覆写通过动态的调用函数prestartCoreThread() or prestartAllCoreThreads(). 如果你想构造这个线程池不包含queue,你或许需要restart threads。
 

创建新的线程

 新的线程可以通过 ThreadFactory来创建. 如果没有指定,defaultThreadFactory() 会被使用,这个方法创建的线程在同一个ThreadGroup 中、有同样的NORM_PRIORITY 优先级、非守护状态。 通过提供一个不同的ThreadFactory, 可以修改线程的名字、线程组、优先级、守护状态等等。如果ThreadFactory创建线程失败了,会从newThread返回一个null, executor 会继续不过不会执行任务tasks。

Keep-alive 时间

 如果线程池中的线程数量超过了corePoolSize , 当额外的线程空闲时间超过keepAliveTime 时会被终止掉。这样可以减少资源浪费当线程池没有被充分使用的时候。当线程池变忙的时候,新的线程会被创建出来。这个参数可以通过setKeepAliveTime(long, TimeUnit)方法来改变。 Long.MAX_VALUE NANOSECONDS参数 可以 禁止空闲线程从是否终止之前到关闭。默认情况下, keep-alive 策略适合于线程池中超过corePoolSize的额外的线程. 但是方法allowCoreThreadTimeOut(boolean) 可以把这个策略应用于core threads,只要keepAliveTime 不为0。

队列

 任意的BlockingQueue 可以被用来传递和保留提交的tasks。queue的使用与线程池的大小有关:

  • 如果线程中的running线程数量 < corePoolSize , Executor 会直接新建一个线程而不会使用queue。
  • 如果线程中的running线程数量 >= corePoolSize, Executor 会把一个请求放入queue而不会新建线程。
  • 如果queue满了,线程中的running线程数量 <= maximumPoolSize,会创建一个新的线程,要不然,这个task就会被拒绝。

有三种常用的queue策略:
1. 直接传递(Direct handoffs)。这个一种同步queue,直接把task传递给thread,不会保留他们。这种情况,如果没有线程立马可用,那么task放入queue就会失败,因此必要要新建一个线程。 这种策略可以在处理多个有依赖关系的请求时防止被锁住。Direct handoffs 通常需要一个无限大的maximumPoolSizes防止新的任务被拒绝。 反过来,也会出现线程无限增长的情况。
2. 无限队列(Unbounded queues). Using an unbounded queue (for example a LinkedBlockingQueue without a predefined capacity) will cause new tasks to wait in the queue when all corePoolSize threads are busy. Thus, no more than corePoolSize threads will ever be created. (And the value of the maximumPoolSize therefore doesn’t have any effect.) This may be appropriate when each task is completely independent of others, so tasks cannot affect each others execution; for example, in a web page server. While this style of queuing can be useful in smoothing out transient bursts of requests, it admits the possibility of unbounded work queue growth when commands continue to arrive on average faster than they can be processed.
3. 有限队列(Bounded queues). 一个有限队列(比如,ArrayBlockingQueue)可以防止资源耗尽,当使用一个有限的maximumPoolSizes。但是不是很有容易协同和控制。 Queue的大小和线程池的最大数可能相互权衡:如果使用大的queue和小的线程池可以减少CPU的使用,系统资源和内存交换支出,但是可能导致人为的低吞吐量;如果使用小的queue和大的线程池,会导致CPUs高运载但是可能遇到不可接受的调度开销,也可能会降低吞吐量。

拒绝任务

 新的任务有可能会被拒绝,当Executor关闭了 或者 线程池和 queue都满了。这种情况下,会调用RejectedExecutionHandler的rejectedExecution(Runnable, ThreadPoolExecutor) 方法.
 4种内置的控制策略:

  1. 默认的是ThreadPoolExecutor.AbortPolicy, 当拒绝的时候会抛出一个运行时异常runtime RejectedExecutionException.
  2. ThreadPoolExecutor.CallerRunsPolicy, 这个线程会调用它自己来执行这个task。
  3. ThreadPoolExecutor.DiscardPolicy, task会被drop。
  4. ThreadPoolExecutor.DiscardOldestPolicy, 如果executor没有关闭,work queque头部的第一个task会被drop,然后尝试重新执行。(如果再次失败,重复动作)
    可以自定义或者使用别的of RejectedExecutionHandler类。不过需要注意一些特殊情况。

Hook methods

  这个类提供了可覆写的protected方法beforeExecute(Thread, Runnable) 和 afterExecute(Runnable, Throwable) ,这个两个方法在每个人物execute之前和之后。 这些可以用来操纵执行环境;例如:重新初始化ThreadLocals, 收集统计或者添加Log。另外,terminated() 可以被覆写来执行一些特俗的操作当Executor一旦完全终止的时候。
如果这些hook的callback方法抛异常了,内部工作现场会一次失败,突然终止。

队列维护

  getQueue() 允许访问queue来监控和debug。 两个可用的方法remove(Runnable) and purge()可以用来管理内存。

结束

  当线程池不会引用并且没有保留线程的时候会被自动关闭。如果你想保证没有引用的线程池被回收,假如你忘了调用shutdown(),那么你必须安排不使用的线程最终die,通过设置合适的 keep-alive times,使用0 core threads 或者 setting allowCoreThreadTimeOut(boolean)。

实例参考:

package demo;

import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPool2

    private static int produceTaskSleepTime = 2;
    private static int produceTaskMaxNumber = 10;

    public static void main(String[] args)
    
        // 构造一个线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 1; i <= produceTaskMaxNumber; i++)
        
            try
            
                // 产生一个任务,并将其加入到线程池
                String task = "task@ " + i;
                System.out.println("put " + task);
                threadPool.execute(new ThreadPoolTask(task));

                // 便于观察,等待一段时间
                Thread.sleep(produceTaskSleepTime);
            
            catch (Exception e)
            
                e.printStackTrace();
            
        
    


/**
 * 线程池执行的任务
 */
class ThreadPoolTask implements Runnable, Serializable

    private static final long serialVersionUID = 0;
    private static int consumeTaskSleepTime = 2000;
    // 保存任务所需要的数据
    private Object threadPoolTaskData;

    ThreadPoolTask(Object tasks)
    
        this.threadPoolTaskData = tasks;
    

    public void run()
    
        // 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
        System.out.println(Thread.currentThread().getName());
        System.out.println("start .." + threadPoolTaskData);

        try
        
            // //便于观察,等待一段时间
            Thread.sleep(consumeTaskSleepTime);
        
        catch (Exception e)
        
            e.printStackTrace();
        
        threadPoolTaskData = null;
    

    public Object getTask()
    
        return this.threadPoolTaskData;
    

实例来源:http://blog.chinaunix.net/uid-20577907-id-3519578.html

以上是关于ThreadPoolExecutor 官方使用说明的主要内容,如果未能解决你的问题,请参考以下文章

将 InheritableThreadLocal 与 ThreadPoolExecutor - 或 - 不重用线程的 ThreadPoolExecutor 一起使用

使用ThreadPoolExecutor进行多线程编程

ThreadPoolExecutor使用介绍

ThreadPoolExecutor使用详解

[转发]对ThreadPoolExecutor初识

java线程池总结(ThreadPoolExecutor与Executors特性及使用)