Java多线程复习与巩固(六)--线程池ThreadPoolExecutor

来源:互联网 发布:一落叶而知天下秋启示 编辑:程序博客网 时间:2024/05/30 23:01

为什么要使用线程池

线程创建与销毁都耗费时间,对于大量的短暂任务如果仍使用“创建->执行任务->销毁”的简单模式,将极大地浪费线程的使用效率(一个线程仅仅处理一个短暂的任务就被销毁了)。在这种情况下,为了提高线程的使用效率,我们使用缓存池的策略让线程执行任务后不立即销毁而是等待着处理下一个任务。

使用Executors工具类

Executors是线程池框架提供给我们的创建线程池的工具类,它里面提供了以下创建几类线程池的方法。

// 创建固定线程数量的线程池public static ExecutorService newFixedThreadPool();// 创建单个线程的线程池public static ExecutorService newSingleThreadExecutor();// 创建无数量限制可自动增减线程的线程池public static ExecutorService newCachedThreadPool();// 创建(可计划的)任务延时执行线程池public static ScheduledExecutorService newScheduledThreadPool();// 单线程版的任务计划执行的线程池public static ScheduledExecutorService newSingleThreadScheduledExecutor();

通过查看这几个方法的源码发现:前三个方法new了ThreadPoolExecutor对象,而后面两个方法new了ScheduledThreadPoolExecutor对象。整个线程池框架的结构图如下,其中ThreadPoolExecutor是本文的核心。

JDK文档建议一般情况使用Executors去创建线程池

线程池ThreadPoolExecutor相关类继承图

其中三个核心接口的方法如下:

三个核心接口的方法

构造ThreadPoolExecutor对象

java.util.concurrent.ThreadPoolExecutor 类是线程池中最核心的类之一,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面分析一下ThreadPoolExecutor类的核心源码的具体实现。

在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {    .....    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) {        //代码省略    }    ...}

  从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService(实现ExecutorService接口)类,并提供了四个构造器,前三个构造器最终都会辗转调用第四个构造器。

  下面解释一下第四个构造器中各个参数的含义:

  • corePoolSize:核心池的大小。

    • 核心池中的线程会一致保存在线程池中(即使线程空闲),除非调用allowCoreThreadTimeOut方法允许核心线程在空闲后一定时间内销毁,该时间由构造方法中的keepAliveTimeunit参数指定;
    • 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这两个方法的名字就可以看出,是“预创建线程”的意思,即在没有任务到来之前就创建corePoolSize个线程(prestartAllCoreThreads)或者一个线程(prestartCoreThread);
  • maximumPoolSize:线程池允许的最大线程数。这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。

    • 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把新加入的任务放到缓存队列当中,缓冲队列由构造方法中的workQueue参数指定,如果入队失败则尝试创建临时线程,但临时线程和核心线程的总数不能超过maximumPoolSize,当线程数达到maximumPoolSize后会拒绝新任务;所以有两种方式可以让任务绝不被拒绝:①将maximumPoolSize设置为Integer.MAX_VALUE(线程数不可能达到这个值);②使用无限容量的阻塞队列LinkedBlockingQueue(所有处理不过来的任务全部排队去)
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。

    • 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用——当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会被销毁,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(true)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

    TimeUnit.DAYS;              //天TimeUnit.HOURS;             //小时TimeUnit.MINUTES;           //分钟TimeUnit.SECONDS;           //秒TimeUnit.MILLISECONDS;      //毫秒TimeUnit.MICROSECONDS;      //微妙TimeUnit.NANOSECONDS;       //纳秒

    并发库中所有时间表示方法都是以TimeUnit枚举类指定单位

  • workQueue:一个阻塞队列,用来存储等待执行的任务,一般来说,这里的阻塞队列有以下几种选择:

    ArrayBlockingQueue    // 数组实现的阻塞队列,数组不支持自动扩容。所以当阻塞队列已满                    // 线程池会根据handler参数中指定的拒绝任务的策略决定如何处理后面加入的任务LinkedBlockingQueue   // 链表实现的阻塞队列SynchronousQueue      // 零容量的同步阻塞队列,添加任务直到有线程接受该任务才返回PriorityBlockingQueue // 小顶堆实现的优先级阻塞队列DelayQueue            // 延时阻塞队列,该队列需要实现Delayed接口                    // 所以所有的任务都会封装成ScheduledFutureTask对象(该类已实现Delayed接口)                    // 延时阻塞队列用在ScheduledThreadPoolExecutor

    有关BlockingQueue的内容可以参考《Java集合框架总结与巩固》

  • threadFactory:线程工厂,主要用来创建线程;默认情况都会使用Executors工具类中定义的默认工厂类DefaultThreadFactory

  • handler:表示当拒绝处理任务时的策略,有以下四种取值(默认为AbortPolicy):

    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

    可通过实现RejectedExecutionHandler接口来自定义任务拒绝后的处理策略

线程池的状态转换

ThreadPoolExecutor类中有一个ctl属性,该属性是AtomicInteger类型,本质上就是32bit的int类型。这个32bit字段中存储了两个数据:

ThreadPoolExecutor.ctl

其中三个高字节位存储了线程池当前的运行状态,线程池状态有以下几个:

    // runState is stored in the high-order bits    private static final int RUNNING    = -1 << COUNT_BITS;    private static final int SHUTDOWN   =  0 << COUNT_BITS;    private static final int STOP       =  1 << COUNT_BITS;    private static final int TIDYING    =  2 << COUNT_BITS;    private static final int TERMINATED =  3 << COUNT_BITS;
  • RUNNING:接受新任务并处理队列中的任务
  • SHUTDOWN:不接受新任务但处理队列中的任务
  • STOP:不接受新任务也不处理队列中的任务并终断正在处理中的任务
  • TIDYING:所有任务已经终止,workerCount等于0,线程池切换到TIDYING后将会执行terminated()钩子方法
  • TERMINATED:terminated()方法已执行完成

整个过程的状态转换图如下:

线程池状态转换图

我们可以调用的两个改变线程池状态的方法分别是:

// 进入SHUTDOWN状态public void shutdown();// 进入STOP状态public List<Runnable> shutdownNow();

另外ThreadPoolExecutor提供了一些方法来查询这些状态:

// 非运行状态:SHUTDOWN,STOP,TIDYING,TERMINATEDpublic boolean isShutdown() {    return ! isRunning(ctl.get());}// 正在终止状态:SHUTDOWN,STOP,TIDYINGpublic boolean isTerminating() {    int c = ctl.get();    return ! isRunning(c) && runStateLessThan(c, TERMINATED);}// 终止状态:TERMINATEDpublic boolean isTerminated() {    return runStateAtLeast(ctl.get(), TERMINATED);}

任务的提交

任务提交主要有两种方式:

  • execute(Runnable command):定义在Executor接口中
  • submit的三个重载方法:定义在ExecutorService接口中

submit提交方式

submit方法的实现源码在ThreadPoolExecutor的基类AbstractExecutorService中:

    // 将Runnable和Callable包装成FutureTask对象    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {        return new FutureTask<T>(runnable, value);    }    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {        return new FutureTask<T>(callable);    }    public Future<?> submit(Runnable task) {        if (task == null) throw new NullPointerException();        RunnableFuture<Void> ftask = newTaskFor(task, null);        execute(ftask);        return ftask;    }    public <T> Future<T> submit(Runnable task, T result) {        if (task == null) throw new NullPointerException();        RunnableFuture<T> ftask = newTaskFor(task, result);        execute(ftask);        return ftask;    }    public <T> Future<T> submit(Callable<T> task) {        if (task == null) throw new NullPointerException();        RunnableFuture<T> ftask = newTaskFor(task);        execute(ftask);        return ftask;    }

submit最终都会调用execute方法去执行任务,区别在于submit方法返回一个FutureTask对象,该对象实现Future接口,Future接口及其相关类继承图如下:

Future继承图

FutureTask类中定义了如下的状态:

    /**     * The run state of this task, initially NEW.  The run state     * transitions to a terminal state only in methods set,     * setException, and cancel.  During completion, state may take on     * transient values of COMPLETING (while outcome is being set) or     * INTERRUPTING (only while interrupting the runner to satisfy a     * cancel(true)). Transitions from these intermediate to final     * states use cheaper ordered/lazy writes because values are unique     * and cannot be further modified.     *     * Possible state transitions:     * NEW -> COMPLETING -> NORMAL     * NEW -> COMPLETING -> EXCEPTIONAL     * NEW -> CANCELLED     * NEW -> INTERRUPTING -> INTERRUPTED     */    private volatile int state;    private static final int NEW          = 0;// 任务已被创建(new的时候默认状态为0)    private static final int COMPLETING   = 1;// 任务即将完成(已获取返回值或已捕获异常)    private static final int NORMAL       = 2;// 任务正常完成(以返回值的形式完成任务)    private static final int EXCEPTIONAL  = 3;// 任务异常完成(任务执行过程发生异常并被捕获)    private static final int CANCELLED    = 4;// 任务已被取消(任务还没被执行就被取消了,可能在排队)    private static final int INTERRUPTING = 5;// 任务正在中断(任务执行时被取消)    private static final int INTERRUPTED  = 6;// 任务已经中断(INTERRUPTING的下一个状态)

FutureTask的状态转换图如下(其中绿色标注的是外部可调用的方法,其他方法均有内部调用,RUNNING状态是我附加的状态,表示该任务已经被运行):

FutureTask状态转换图

Future接口定义了以下几个方法:

// 尝试取消任务,根据任务的执行状态分一下几种情况// 任务完成或任务已取消:该方法调用失败返回false// 运行状态:任务正在执行,根据传入mayInterruptIfRunning参数判断是否应该终断任务// 排队状态:任务正在排队,则将任务状态设置为CANCELLED,线程池不会执行已经被取消的任务boolean cancel(boolean mayInterruptIfRunning);// 任务是否被取消,CANCELLED,INTERRUPTING,INTERRUPTED状态下返回trueboolean isCancelled() { return state >= CANCELLED; }// 任务是否被执行,只有NEW状态返回falseboolean isDone(){ return state != NEW; }// 获取任务执行结果,该方法会一直阻塞等待任务的执行结果V get() throws InterruptedException, ExecutionException;// 获取任务执行结果,该方法会等待timeout时间,// 如果没有结果就抛出超时异常(TimeoutException)V get(long timeout, TimeUnit unit)        throws InterruptedException, ExecutionException, TimeoutException;

示例:

public class ThreadPoolExecutorTest {    public static void main(String[] args) {        // 线程池大小固定为4,处理不完的任务全部入队        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4, 0, TimeUnit.SECONDS,                new LinkedBlockingQueue<>());        Scanner sc = new Scanner(System.in);        System.out.println("----------------------------------------");        System.out.println("输入0使用submit提交任务到线程池");        System.out.println("输入正整数使用取消上次提交的任务并允许中断");        System.out.println("输入负整数使用取消上次提交的任务不允许中断");        System.out.println("输入其他字符关闭线程池并退出程序");        System.out.println("----------------------------------------");        Future<String> task = null;        while (true) {            if (sc.hasNextInt()) {                int command = sc.nextInt();                if (command == 0) {                    // 提交任务                    task = threadPool.submit(new Callable<String>() {                        @Override                        public String call() throws Exception {                            printMessage("Task start");                            Thread.sleep(1800);                            printMessage("Task finish");                            return "result";                        }                    });                } else if (command > 0 && task != null) {                    // 强制取消任务                    boolean success = task.cancel(true);                    System.out.println(task + ":task.cancel(true)->"                            + (success ? "success" : "failed"));                } else if (command < 0 && task != null) {                    // 取消任务                    boolean success = task.cancel(false);                    System.out.println(task + ":task.cancel(false)->"                            + (success ? "success" : "failed"));                }            } else {                threadPool.shutdownNow();                break;            }        }    }    private static void printMessage(String msg) {        String name = Thread.currentThread().getName();        System.out.println(name + ":" + msg);    }}

execute提交方式

submit最终都会调用execute方法去执行任务,所以我们重点需要分析execute方法:

    public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        int c = ctl.get();        if (workerCountOf(c) < corePoolSize) {            // 如果核心线程未满,则创建核心线程并将任务交由新线程处理(即使有核心线程空闲)            // 所以前corePoolSize个任务都会一次交给新创建的核心线程执行            if (addWorker(command, true))                return;            c = ctl.get();        }        // 往下走说明核心线程已全部创建完毕        if (isRunning(c) && workQueue.offer(command)) {            // 如果线程池仍是RUNNING状态,将任务加入工作队列            int recheck = ctl.get();            if (! isRunning(recheck) && remove(command))            // 如果线程池已经SHUTDOWN,将任务回滚移除工作队列并拒绝该任务                reject(command);            else if (workerCountOf(recheck) == 0)            // 该情况用于核心线程数(corePoolSize)为0            // 或者允许核心线程超时(allowCoreThreadTimeOut)的时候                addWorker(null, false);        }        // 入队失败则尝试创建普通临时线程(非核心线程)        else if (!addWorker(command, false))            // 如果仍无法创建线程            // 可以断定线程池已经SHUTDOWN(关闭)或者SATURATED(饱和)            // 所以拒绝该任务            reject(command);    }

根据源码可以得出以下执行流程:

  1. 如果正在运行的线程数小于corePoolSize,Executor总是添加一个新线程,而不是任务排队。
  2. 如果已有corePoolSize或更多线程在运行,则Executor总是优先选择任务排队而不会添加新线程。
  3. 如果任务请求不能排队,则创建一个新线程,但是线程总数量不超过maximumPoolSize,如果不能创建新线程任务将会被拒绝。

这里有几点需要重点注意:

  • 第五行workerCountOf(c) < corePoolSize:如果核心线程没有创建完则新任务交由新创建的核心线程处理,但是我们如果调用prestartAllCoreThreads方法预先把所有核心线程创建完成,则这一分支不会执行。
  • 第十三行workQueue.offer(command):offer方法不会阻塞(名不符其实,阻塞队列不阻塞呵呵),如果入队成功会立即返回true否则返回false。入队成功与否取决于workQueue的性质。比如:①单链表实现的LinkedBlockingQueue默认容量为Integer.MAX_VALUE(等价于无限容量),所以此时该方法不会返回false也不会创建临时线程(都去排队去了),当然如果创建LinkedBlockingQueue时指定了capacity,offer方法就可能返回false,但我们一般不会这么干;②而数组实现的ArrayBlockingQueue不允许扩容,所以队列已满则会返回false进入二十三行尝试创建临时线程,如果总线程数不超过maximumPoolSize则能创建临时线程,但会导致后来的任务没排队反而能得到执行(不公平),如果超出maximumPoolSize创建临时线程失败则会拒绝任务,两种情况都不好,所以ArrayBlockingQueue用的不是很多;③使用小顶堆实现的PriorityBlockingQueue会根据任务的优先级来选择执行顺序;④使用没有容量的同步队列SynchronousQueue,如果没有空闲线程接收任务会立即返回false,所以大部分情况会创建新的临时线程。
  • 第十七行workerCountOf(recheck) == 0:这句判断间接表示核心线程数为0的情况,核心线程数为0只会发生在两种条件下:①线程池本身已经指定核心数为0(构造方法指定或setCorePoolSize方法指定),②调用allowCoreThreadTimeOut方法允许核心线程超时导致核心线程数位0。

线程池的其他变量和方法

其他成员变量:

 // 工作队列:用于任务排队等待private final BlockingQueue<Runnable> workQueue; // 操作线程池数据时,用于保持同步的锁private final ReentrantLock mainLock = new ReentrantLock(); // 保存所有工作线程的集合private final HashSet<Worker> workers = new HashSet<Worker>(); // 调用awaitTermination方法时等待终止的条件private final Condition termination = mainLock.newCondition(); // 线程池达到的最大线程数,记录曾经出现过的最大线程数private int largestPoolSize; // 已经完成任务的数量private long completedTaskCount; // 用于创建线程的线程工厂private volatile ThreadFactory threadFactory; // 任务的拒绝策略private volatile RejectedExecutionHandler handler; // 线程存活时间private volatile long keepAliveTime; // 是否允许核心线程超时private volatile boolean allowCoreThreadTimeOut; // 核心线程数private volatile int corePoolSize; // 线程池允许的最大线程数private volatile int maximumPoolSize;

其他成员方法:

//------------------------------------------------// 与线程大小有关的方法:// 这几个数量之间满足:CorePoolSize <= PoolSize <= LargestPoolSize <= MaximumPoolSize// 核心线程数public void setCorePoolSize(int corePoolSize);public int getCorePoolSize();// 最大线程数public void setMaximumPoolSize(int maximumPoolSize);public int getMaximumPoolSize();// 当前线程池大小public int getPoolSize();// 线程池达到的最大线程数public int getLargestPoolSize();//------------------------------------------------// 和任务数量有关的方法:// 当前活跃的任务数量:这个数量等于当前工作线程个数(不包括空闲线程)public int getActiveCount();// 线程池已接受的任务总量public long getTaskCount();// 线程池已经完成的任务数量public long getCompletedTaskCount();//------------------------------------------------// 其他getter/setter方法:// threadFactory的getter/setter方法public void setThreadFactory(ThreadFactory threadFactory);public ThreadFactory getThreadFactory();// handler的getter/setter方法public void setRejectedExecutionHandler(RejectedExecutionHandler handler);public RejectedExecutionHandler getRejectedExecutionHandler();// allowCoreThreadTimeOut的getter/setter方法public void allowCoreThreadTimeOut(boolean value);public boolean allowsCoreThreadTimeOut();// keepAliveTime的getter/setter方法public void setKeepAliveTime(long time, TimeUnit unit);public long getKeepAliveTime(TimeUnit unit);//------------------------------------------------// 其他方法// 预启动一个核心线程public boolean prestartCoreThread();// 预启动所有核心线程public int prestartAllCoreThreads();// 获取等待队列public BlockingQueue<Runnable> getQueue();// 将task任务从等待队列中移除public boolean remove(Runnable task);// 将等待队列中的所有已经被取消的Future任务清除public void purge();// 在shutdown请求之后,调用awaitTermination方法// 会阻塞等待任务完成进入TERMINATED状态// timeout设置等待时间,unit设置时间单位public boolean awaitTermination(long timeout, TimeUnit unit);

扩展ThreadPoolExecutor的功能

在ThreadPoolExecutor类中还有三个protected属性的空方法:

// 任务执行之前:// Thread t:执行任务的线程// Runnable r:被执行的任务protected void beforeExecute(Thread t, Runnable r) { }// 任务执行之后:// Runnable r:被执行的任务// Throwable t:任务执行过程中抛出的异常protected void afterExecute(Runnable r, Throwable t) { }// 线程池进入TERMINATED状态后protected void terminated() { }

根据JDK文档中的说明,我们可以重载这三个钩子方法来对ThreadPoolExecutor进行扩展。下面是官方文档提供的扩展示例:

// 可以暂停执行的线程池class PausableThreadPoolExecutor extends ThreadPoolExecutor {    public PausableThreadPoolExecutor(...) { super(...); }    private boolean isPaused;    private ReentrantLock pauseLock = new ReentrantLock();    private Condition unpaused = pauseLock.newCondition();    protected void beforeExecute(Thread t, Runnable r) {        super.beforeExecute(t, r);        pauseLock.lock();        try {            // 如果线程池被暂停,则任务等待不暂停的条件(等待resume方法的调用)            while (isPaused)                unpaused.await();        } catch (InterruptedException ie) {            t.interrupt();        } finally {            pauseLock.unlock();        }    }    public void pause() {        pauseLock.lock();        try {            isPaused = true;// 暂停        } finally {            pauseLock.unlock();        }    }    public void resume() {        pauseLock.lock();        try {            isPaused = false;// 恢复            unpaused.signalAll();        } finally {            pauseLock.unlock();        }    }}
阅读全文
0 0
原创粉丝点击