线程池 (2)常用方法及特殊线程池

425 阅读4分钟

常用方法

在上节中我们知道,任务最终的执行与Worker类有关,线程池中有多少个Worker就代表有多少个线程

submit(Callable<T> task)submit(Runnable task):将任务封装成FutureTask对象,它是RunnableFuture的实现类,内含状态、结果等成员变量,如果是Callable实现类,会调用call()方法,如果是Runnable实现类,会调用run()方法。用get()将会阻塞直到出结果,对于返回类型是void的则返回null

invokeAll(Collection<? extends Callable<T>> tasks):tasks是Callable接口的实现类集合,执行实现类任务,阻塞至所有任务完成

shutdown():将线程池状态置为SHUTDOWN,停止接收新任务,然后尝试中断等待阻塞队列任务的Worker线程

ThreadPoolExecutor->interruptIdleWorkers():
    for (Worker w : workers) {
        Thread t = w.thread;
        // w是worker类,tryLock()只有状态为0的时候才会成功
        // 注意到任务未执行完毕的状态是1,所以这边就不会中断
        if (!t.isInterrupted() && w.tryLock()) {
            try {
                t.interrupt();
                ......

shutdownNow():将线程池状态置为STOP,然后停止线程池中的Worker线程(无论是否完成),返回阻塞队列中的任务

ThreadPoolExecutor->interruptWorkers():
    for (Worker w : workers)
        w.interruptIfStarted();

getPoolSize():返回线程池中的Worker线程数量

getActiveCount():返回线程池中正在执行任务的Worker线程数量

getCompletedTaskCount():返回线程池完成任务的数量,由completedTaskCount与各Worker线程相加

prestartCoreThread():添加一个核心工作线程

prestartAllCoreThreads():添加核心工作线程,直到达到corePoolSize的数量

setCorePoolSize(int corePoolSize):将核心线程数设为corePoolSize,如果此时工作线程数大于核心线程数,尝试中断等待阻塞队列任务的Worker线程;如果扩大了核心线程数,尝试将阻塞队列中的任务添加Worker线程

setMaximumPoolSize(int maximumPoolSize):将最大线程数设为maximumPoolSize,如果此时工作线程数大于最大线程数,尝试中断等待阻塞队列任务的Worker线程

setKeepAliveTime(long time, TimeUnit unit):重新设置keepAliveTime+unit,如果此时间隔设小的话,尝试中断等待阻塞队列任务的Worker线程

特殊线程池

单例线程池

SingleThreadExecutor()一个corePoolSize,超出全部放入LinkedBlockingQueue阻塞队列

固定线程池

FixedThreadExecutor(n):n个corePoolSize,超出全部放入LinkedBlockingQueue阻塞队列

缓存线程池

CachedThreadPool:0个corePoolSize,阻塞队列SynchronousQueue不接收任务,Integer.MAX_VALUE个maximumPoolSize,60秒没获取到任务则Worker退出

定时线程池

ScheduledThreadPool(n):n个corePoolSize,先将任务放入DelayedWorkQueue阻塞队列,然后启动核心线程。返回ScheduledThreadPoolExecutor对象。

scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):假设这里的unit是秒,initialDelay是2,period是3,那么延时2s之后启动,3s后,如果任务完成则立即执行下一次,否则等待任务完成之后执行下一次。返回ScheduledFutureTask对象。

scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit):假设这里的unit是秒,initialDelay是2,period是3,那么延时2s之后启动,任务完成3s后,执行下一次。返回ScheduledFutureTask对象。

ScheduledThreadPoolExecutor->execute():
    schedule(command, 0, NANOSECONDS);
ScheduledThreadPoolExecutor->schedule():
    RunnableScheduledFuture<?> t = decorateTask(command,
        new ScheduledFutureTask<Void>(command, null,
                                      triggerTime(delay, unit)));
    // 将任务添加进阻塞队列,如果工作线程数小于核心线程数,那么启动一个工作线程
    delayedExecute(t);
ScheduledThreadPoolExecutor->scheduleAtFixedRate():
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period));
ScheduledThreadPoolExecutor->scheduleWithFixedDelay():
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(-delay));

triggerTime(delay, unit),计算出将要执行任务的纳秒时间,如果是0,那么就是当前的纳秒时间;如果delay是3,unit是秒,那么就是当前时间+3s的纳秒时间。

ScheduledThreadPoolExecutor->ScheduledFutureTask->ScheduledFutureTask(Runnable r, V result, long ns, long period):
    super(r, result);
    // 执行任务的纳秒时间
    this.time = ns;
    // 时间周期
    this.period = period;
    this.sequenceNumber = sequencer.getAndIncrement();

注意到三种方法指定了不同的时间周期,其中execute()是0,scheduleAtFixedRate()是一个正数,scheduleWithFixedDelay()是一个负数。

DelayedWorkQueue重写阻塞队列常用方法,以take()为例

ScheduledThreadPoolExecutor->DelayedWorkQueue->take():
    RunnableScheduledFuture<?> first = queue[0];
    if (first == null)
        // 如果没有任务,就一直阻塞
        // 调用add()添加任务时,会调用available.signal(),这里就会唤醒
        available.await();
    else {
        // 当前时间与执行任务时间的差值
        long delay = first.getDelay(NANOSECONDS);
        // 差值小于0,说明已经过了任务时间,需要执行任务
        if (delay <= 0)
            return finishPoll(first);
        first = null;
        ......
        // 否则,假如相差了一秒,那就让线程休眠一秒后再尝试
        available.awaitNanos(delay);

任务完成后,会根据不同的方法计算出下一次执行的时间。

ScheduledThreadPoolExecutor->ScheduledFutureTask->run():
    // 周期是否等于0
    boolean periodic = isPeriodic();
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    // 如果等于0,就普通的执行
    else if (!periodic)
        ScheduledFutureTask.super.run();
    // 执行任务,若任务状态不为0则返回false
    else if (ScheduledFutureTask.super.runAndReset()) {
        // 设置下一次执行任务的时间
        setNextRunTime(); // --1
        // 再次放入阻塞队列
        reExecutePeriodic(outerTask);
    }
        
ScheduledThreadPoolExecutor->ScheduledFutureTask->setNextRunTime(): // --1
    long p = period;
    if (p > 0)
        // 直接计算出下一次的执行时间,对应的是scheduleAtFixedRate()方法
        time += p;
    else
        // 下一次的执行时间=当前时间+时间周期,对应的是scheduleWithFixedDelay()方法
        time = triggerTime(-p);

scheduledFuture.cancel(boolean mayInterruptIfRunning):取消任务,若mayInterruptIfRunning为true则中断线程。原理是改变任务状态,当状态不为0的时候,直接跳过执行任务和计算下一次任务执行时间的步骤。

应用:SpringBoot中定时任务注解@EnableScheduling的实现