ThreadPoolExecutor 分析

ThreadPoolExecutor 分析

1. 继承关系

从下面的图可以看出,根就是Executor接口,下面就挨个来看看这些接口和抽象类干了什么样的事情。

在这里插入图片描述

Executor接口

这个接口用于执行一个任务,不要求这个任务同步,异步,任务怎么执行很宽泛。

execute方法用于执行任务,如果任务不能执行,抛出RejectedExecutionException,如果任务为空,抛出NullPointerException,这两个异常都是非检查型异常,不需要强制声明。

public interface Executor {
    void execute(Runnable command);
}

ExecutorService接口

ExecutorService继承于Executor,在之前的基础上,增加了几个方法,主要是提供了管理终端,这些方法可以为一个或者多个异步任务生成对于的future对象。

ExecutorService可以关闭,关闭之后就不能继续接受新的任务了,有两个关闭的方法

  1. shutdown

    在终止之前,会将之前提交的任务运行完。不会接受新任务

  2. shutdownNow

    不会接受新任务,会尝试中断现在正在执行的任务

并且,在之前继承上,新增了一个submit方法,submit方法是基于Executor.execute(Runnable)方法创建的,他可以返回一个future,用于任务的取消和或者等待任务完成。

invokeAnyinvokeAll方法用于批量执行任务。

Executors方法提供了工厂方法来创建ExecutorService



package java.util.concurrent;
import java.util.List;
import java.util.Collection;


public interface ExecutorService extends Executor {
    //执行前面提交的任务,但不会接受新任务。 如果已经关闭,多次调用没有啥问题。
    //这个方法不等待先前提交的任务完成执行。 使用 awaitTermination 来做到这一点。
    void shutdown();

    // 中断所有正在执行的任务,并且返回等待执行任务的集合
    // 这个方法不会等待主动执行的任务终止。 使用 awaitTermination 来做到这一点。
    List<Runnable> shutdownNow();

    
    boolean isShutdown();

  // 如果关闭后所有任务都已完成,则返回 true。
 //必须先调用 shutdown 或 shutdownNow,否则isTerminated 不可能为真。
    boolean isTerminated();

    //  阻塞直到所有任务在关闭请求后完成执行,或发生超时,或当前线程被中断,按照先发送的来
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;


    <T> Future<T> submit(Callable<T> task);
   
    //下面的提交方式,这个提交方式和future的一样,一个Runnable,在来一个T,操作一拨,直接返回 
    <T> Future<T> submit(Runnable task, T result);

    
    Future<?> submit(Runnable task);

   
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

   
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

   
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

   
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

AbstractExecutorService抽象类

ExecutorService的默认实现,重点关注 submit方法,这个方法将传递进来的方法分装为Future,交给execute方法执行。这里分装Future对象的操作和之前Future实现分析里面逻辑一样,复用了那一块的代码。

package java.util.concurrent;
import java.util.*;

/**
 ExecutorService的默认实现
 */
public abstract class AbstractExecutorService implements ExecutorService {

     // 将提交的Runnable封装为一个FutureTask接口。
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

     //下面是也是callable的封装
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

    //提交任务,空指针检查,构建新的task,调用execute方法执行任务。execute是给具体的类来实现的。
    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();
       // 和上面的不一样,newTaskFor只是封装了不同的对象
        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;
    }

     // 这个方法很简单,只要提交的任务里面一个完成就好了,
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                              boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (tasks == null)
            throw new NullPointerException();
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
       // 构建futures集合
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
        // 装饰者模式。将当前的执行器利用ExecutorCompletionService包装。
        ExecutorCompletionService<T> ecs =
            new ExecutorCompletionService<T>(this);

      

        try {
           // 其实就是循环调用一个任务,重写了FutureTask,在任务完成之后将任务放在一个queue中,如果queue中有一个,就完成了,直接返回就行
            ExecutionException ee = null;
           //这不用说了,经典的等待超时的写法
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Iterator<? extends Callable<T>> it = tasks.iterator();

          // 提交一个任务,将任务封装为QueueingFuture,注意这里是只是提交第一个集合中的第一个任务
          futures.add(ecs.submit(it.next()));
            // ntasks一开始的值是任务的数量,现在ntasks代表的意思就是还没有执行的任务
            --ntasks;
            // 现在提交的任务中执行的任务数量为1
            int active = 1;
         // 死循环开始
            for (;;) {
                 // 从queue中出队
                Future<T> f = ecs.poll();
                // 没有任务就在提交一个任务
                if (f == null) {
                   //如果还有没有执行的任务
                    if (ntasks > 0) {
                       // 能执行的任务数--
                        --ntasks;
                       //继续提交一个任务
                        futures.add(ecs.submit(it.next()));
                       //活动的数量++
                        ++active;
                    }
                   // 如果没有活动的任务,跳出循环
                    else if (active == 0)
                        break;
                   //如果需要 ntasks<=0了,说明所有的任务都已经提交了,那就只能等待了,不能提交了
                    else if (timed) {
                       //等待,
                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                       // 如果唤醒之后还是没有,就抛异常
                        if (f == null)
                            throw new TimeoutException();
                       // 延时等待
                        nanos = deadline - System.nanoTime();
                    }
                    // 如果不需要超时等待,就只能无限期的take
                    else
                        f = ecs.take();
                }
               // 如果有任务,就直接get就行,
                if (f != null) {
                    --active;
                    try {
                        return f.get();
                    } catch (ExecutionException eex) {
                        ee = eex;
                    } catch (RuntimeException rex) {
                        ee = new ExecutionException(rex);
                    }
                }
            }

            if (ee == null)
                ee = new ExecutionException();
            throw ee;

        } finally {
           // 在完成之后,将所有的任务都取消掉
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
        }
    }

    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
        try {
            return doInvokeAny(tasks, false, 0);
        } catch (TimeoutException cannotHappen) {
            assert false;
            return null;
        }
    }

    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        return doInvokeAny(tasks, true, unit.toNanos(timeout));
    }
   //循环所有的任务,循环提交任务,将Future放在一个集合里面,循环future集合,等待或者返回
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks) {
                RunnableFuture<T> f = newTaskFor(t);
                futures.add(f);
                execute(f);
            }
            for (int i = 0, size = futures.size(); i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    try {
                        f.get();
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    }
                }
            }
            done = true;
            return futures;
        } finally {
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }

    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks)
                futures.add(newTaskFor(t));

            final long deadline = System.nanoTime() + nanos;
            final int size = futures.size();

            // Interleave time checks and calls to execute in case
            // executor doesn't have any/much parallelism.
            for (int i = 0; i < size; i++) {
                execute((Runnable)futures.get(i));
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L)
                    return futures;
            }

            for (int i = 0; i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    if (nanos <= 0L)
                        return futures;
                    try {
                        f.get(nanos, TimeUnit.NANOSECONDS);
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    } catch (TimeoutException toe) {
                        return futures;
                    }
                    nanos = deadline - System.nanoTime();
                }
            }
            done = true;
            return futures;
        } finally {
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }

}

2. ThreadPoolExecutor分析(重点是execute方法分析)

线程池解决了两个问题

  1. 执行大量异步任务的性能提升
  2. 对任务的管理

Executors提供了工厂方法,提供了几个线程池

  1. Executors.newCachedThreadPool
  2. Executors.newFixedThreadPool
  3. Executors.newSingleThreadExecutor
  4. Executors.newWorkStealingPool
  5. Executors.newSingleThreadScheduledExecutor
  6. Executors.newScheduledThreadPool

注意:这里不会讲解上面的线程池是什么意思,怎么用,这是一篇从源码分析,线程池执行任务。要想了解具体的用法,这篇文章不合适

自定义线程池的几个重要的点

  1. Core(核心线程数) and maximum pool (最大线程数) sizes

    ThreadPoolExecutor 会根据 corePoolSize和 maximumPoolSize 的值来设置线程池中线程的大小。 提交新任务,如果运行的的线程少于 corePoolSize 时,即使其他工作线程空闲,也会创建一个新线程来处理请求,知道超过了corePoolSize。

    如果线程数超过 corePoolSize 但小于 maximumPoolSize,只有在队列已满时才会创建新线程(一直创建到maximumPoolSize)。 如果corePoolSize和maximumPoolSize一样,就会创建一个大小固定的线程池。

    corePoolSize和maximumPoolSize 只能通过构造方法或者 setCorePoolSize, setMaximumPoolSize 设置。但是后者可以动态的更改线程池的大小

  2. 提前创建线程

    默认线程是任务来的时候(提交任务)才会创建,来一个,创建一个,但是可以提前创建corePoolSize和maximumPoolSize的线程数

    prestartCoreThread

    prestartAllCoreThreads

  3. 创建线程

    创建线程是通过ThreadFactory创建,默认是Executors.defaultThreadFactory,默认创建的线程都是同一个ThreadGroup,同一个优先级,都不是守护线程,通过ThreadFactory接口,可以修改线程的名字,线程组,线程优先级,是否是守护线程等等,ThreadFactory接口如果返回了一个null,线程池不会报错,但是不会执行任务。

  4. Keep-alive times

    线程池中线程数量超过 corePoolSize ,如果空闲时间超过 keepAliveTime,超出corePoolSize的线程将被终止。可以使用 setKeepAliveTime(long, TimeUnit)方法动态更改此参数。 使用 Long.MAX_VALUE TimeUnit.NANOOSECONDS 值可以有效地禁止空闲线程在关闭之前终止。 默认情况下,这个策略仅在线程数超过 corePoolSize 有作用。

    但是``allowCoreThreadTimeOut(boolean) `方法,会尝试中断所有空闲的线程。

  5. Queuing

    任何实现了BlockingQueue接口的方法都可以

    • 如果线程的数量小于corePoolSize,每次提交任务都是创建新的线程
    • 超过corePoolSize,任务会提交到BlockingQueue
    • BlockingQueue放不下,创建线程数到maximumPoolSize
    • 超过maximumPoolSize,任务就会被拒绝
  6. Rejected tasks

    Executor 已经关闭,或者 Executor 的任务已经饱和的时候,继续提交任务就会被拒绝。

    在满了之后,调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)方法。有四个默认的拒绝策略

    • 默认的是ThreadPoolExecutor.AbortPolicy,拒绝的时候抛出 RejectedExecutionException。
    • ThreadPoolExecutor.CallerRunsPolicy,让调用者自身执行任务,可以减慢提交新任务的速度。
    • ThreadPoolExecutor.DiscardPolicy,,啥都不干,这是简单的丢掉。
    • ThreadPoolExecutor.DiscardOldestPolicy,如果executor没有关闭,工作队列头部的任务就会被丢弃,然后通过executor来重新调用execute方法提交任务。
  7. Hook methods

    子类可以重写beforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable)方法来在执行任务之前做操作,比如ThreadLocal的操作,日志的打印。

  8. 队列的维护

    通过getQueue()方法能获取到任务队列。通过任务队列就可以获取当前有多少任务在执行和队列的信息

  9. 终止

    程序中不再引用线程池,并且池中没有剩余线程,线程池将自动关闭。如果您想确保即使用户忘记调用 shutdown 也能回收未引用的池,设置TTL,并且调用allowCoreThreadTimeOut(boolean)方法。这样就可以在没有shutDown的方法下面,也是可以关掉的。

ctl的说明

ctl是一个原子整数,在一个字段里面封装了两个字段

  • workerCount,表示有效线程数
  • runState,表示线程池的状态,包括正在运行,正在关闭等等。

因为将两个字段融为一个int类型了, 所以将 workerCount 限制为 (2^29)-1,而不是 (2^31)-1。这可能大概是一个问题,如果workerCount超过就不太好了,只能将int变为AtomicLong,并且调整移位/掩码。但是,int比AtomicLong快。

runState 主要提供了线程池的生命周期控制,值域有

RUNNING:接受新任务并处理队列里面的任务

SHUTDOWN:不接受新任务,但处理队列里面的任务

STOP:不接受新任务,不处理队列里面的任务, 并中断正在进行的任务

TIDYING:所有任务都已终止,workerCount 为零,转换到状态 TIDYING 的线程将运行 terminate() 钩子方法

TERMINATED: terminate() 已完成

runState上面的取值的几个状态,会随着时间增加,但不会命中所有的状态,也就是说,线程池的状态,只会从running开始,一直增加,不会退回去,并且,上面的状态都不需要全部都走一遍。

几个状态的转化如下图所示

在这里插入图片描述

Worker类说明

从继承和实现的关系可以看到,继承与AQS,并且实现了Runnable接口。

问题:

  1. 为什么要继承于AQS?继承AQS,这是可重入的吗?

    继承与AQS是为了在执行任务的时候,保证不会被打扰。

    不是可重入锁。0代表没有锁,1代表添加了锁,-1表示初始状态

    1. 为什么要实现Runnable接口。

    Runnable接口就是一个代理方法,将自己作为new thread 的参数传递进去,在线程运行的时候,通过Worker里面的run方法调用到 runWorker(this)方法,来真正的执行任务。

关于属性的解释如下

  1. thread:线程的引用,如果ThreadFactory创建线程失败,这就是个null
  2. firstTask:这个worker的第一个任务。
  3. completedTasks:每个线程完成的任务数量

关于AQS加锁和释放锁的操作就不在这里说了,里面的代码也很简单,重点看一下interruptIfStarted方法。

在这个方法里面,只要这个worker启动了,并且worker里面的Thread没有中断过,就会调用work里面的Thread的interrupt方法。

   private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
       
        private static final long serialVersionUID = 6138294804551838833L;

        final Thread thread;
        Runnable firstTask;
        
        volatile long completedTasks;

       
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        public void run() {
            runWorker(this);
        }

      
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

runWorker方法分析

真正执行task的地方,while循环,一直从队列中获取task或者从worker的firstTask中获取task,判断线程池的状态,利用try catch来做逻辑控制,利用标志位来区分worker是否是正常结束,在执行方法之前和之后调用hook方法。

问题

  1. 在开始的时候为啥获取Thread.currentThread(),而不是worker里面的Thread属性

    这俩其实是一样的,runWorker方法是在Worker类的run方法里面调用的,而Thread属性就是通过ThreadFactory创建的,并且在创建的时候将Worker传递进去了,这说明这俩是一致的。

  2. Worker里面的firstTask能否再次赋值

    不能,只有在创建Worker的时候才会赋值,此外,在运行的时候,只能通过从任务队列中获取task

  3. task异常之后,这个task算完成吗?

    算,在finally里面的代码肯定是要运行的。

  4. 什么样的情况下,会跳出while循环

    有两种情况,正常和异常

    • 异常,task异常就会先捕获原始异常,然后再次会抛出。利用try catch 来打破循环
    • 正常结束,如果从任务队列中没有获取到task,就会正常的结束。退出循环。(超过核心线程的线程数的回收)
// 需要注意,
final void runWorker(Worker w) {
        // 当前线程,为啥不是w里面的thread
        Thread wt = Thread.currentThread();
        //拿到worker的firstTask的引用,将worker里面的task变为null
       // 拿到task就是运行的任务的引用
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 一上来先解锁,意味着从这里之后可以用线程来获取锁。只要拿到锁,别的就不能获取到锁了。
        w.unlock(); 
      
      //突然完成的标志位,这个标志位有啥意思。
        boolean completedAbruptly = true;
        try {
           // 一直从task开始循环,一直从队列中获取值。
            while (task != null || (task = getTask()) != null) {
               // 获取锁,意味着这个任务只能这个work运行,并且work运行的时候,不会被别的work中断。
                w.lock();
              
               //如果线程池的状态大于stop,或者当前线程中断中断了,就将当前线程中断。
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                  
                   //执行前的回调方法,要注意这里的try catch的逻辑
                   // 利用try catch来实现逻辑。很巧妙
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                      // 运行任务
                        task.run();
                       // 抛出异常之后,异常会有处理,
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                       //执行之后的回调方法,写在finally里面肯定会执行的。
                        afterExecute(task, thrown);
                    }
                } finally {
                  // 将task变为null,work完成的task数量++。解锁,表示这个任务已经执行结束。
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
               // 正常执行任务(任务不会出错) while循环到这里就结束了,一旦出现了异常,就会利用try catch机制打破while循环
               // 直接到下面的finally,
            }
           // completedAbruptly表示,当前的worker是否是突然终止的(异常终止)
          // 如果take没有获取到任务,那么这个worker就是正常的退出。(比如非核心线程回收中断操作,这种就是正常的结束)
            completedAbruptly = false;
        } finally {
           // 处理线程退出、用completedAbruptly来区分是否是突然中断或者正常中断
            processWorkerExit(w, completedAbruptly);
        }
    }
getTask方法分析

从任务队列中获取task这里会有TTL机制。

这里用到了allowCoreThreadTimeOut,从这里可以看出,超时等待是要在整个queue中等待超时。也就是在队列中任务处理完成之后,在经历TTL之后还是没有获取到任务,超过核心线程数的work就会被销毁。

问题

  1. timedOut和timed,在死循环里面的角色是啥?

       if ((wc > maximumPoolSize || (timed && timedOut))
                    && (wc > 1 || workQueue.isEmpty())) {
                    if (compareAndDecrementWorkerCount(c))
                        return null;
                    continue;
                }
    

    这里的代码会在什么样的情形下面起作用。

    没有看懂这里的目的

  private Runnable getTask() {
        boolean timedOut = false;  // 等待超时标志
        //一开始死循环冲起来,
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

           // 如果线程池的状态大于等于SHUTDOWN,并且状态大于stop或者队列是空的,说明不能获取任务,
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

         //allowCoreThreadTimeOut又出现了,是否运行所有的线程都适用TTL,
          // timed等待超时标志位,如果allowCoreThreadTimeOut不为true,则只有大于corePoolSize的时候才会为true,表示需要等待
          boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
             //(如果当前的workCount的数量大于   maximumPoolSize || 等待并且等待成功。) &&
           // (workCount > 1 || 队列是空的)
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
               // 是否需要等待,如果需要的话,就利用keepAliveTime。,等待超时。不需要就一直等待
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
               
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
processWorkerExit方法分析

处理worker线程退出之后的操作,将需要退出的worker移出worker的集合。调用tryTerminate方法,然后判断,移除这个worker之后,是否要添加新的worker。这里分为两种情况:

  1. worker是正常退出的,通过allowCoreThreadTimeOut来确定池中应该存在的最小的worker,如果小于就创建。
  2. worker是异常退出的。创建一个新的worker。

注意,此出,worker的firstTask是null。

下面有tryTerminate()方法分析。

 private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果是异常中断的worker,就需要减少worker的数量
        if (completedAbruptly) 
            decrementWorkerCount();
        // 拿到mainLock,因为要对worker的集合操作,所以要获取mainLock
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
          // completedTaskCount是ThreadPoolExecutor里面的一个属性,表示完成的task的数量
            completedTaskCount += w.completedTasks;
           // 将需要退出的worker从集合中移除
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        
    
        tryTerminate();

        int c = ctl.get();
        // 运行状态比stop小。
        // 比stop小的就是RUNNING和SHUTDOWN状态
        if (runStateLessThan(c, STOP)) {
           //如果不是异常结束的worker
            if (!completedAbruptly) {
                // min表示最小的应该存活最小的worker。
               // 如果设置了allowCoreThreadTimeOut为true,超时时间(TTL)适用于核心线程
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // 如果最小是0 但是workQueue不是空白的
                if (min == 0 && ! workQueue.isEmpty())
                   // 最小的变为1
                    min = 1;
                if (workerCountOf(c) >= min)
                   // 如果现在存活的worker大于等于min,就不会创建新的worker。
                    return; 
            }
           
            // 到这里就要创建worker
           // 创建新的worker有两个机会
          // 1. 异常,就会创建新的worker代替,之前的任务就结束了 
         // 2. 正常结束,会判断池中存在的worker如果小于min,就会创建新的worker来代替它。
            addWorker(null, false);
        }
    }

tryTerminate方法分析

一开始就死亡循环。在三种情况下(线程池在运行,状态大于等于TIDYING,状态等于SHUTDOWN 并且 任务队列不是空的),是不会终止的。如果当前的workerCount不是0,只会中断一个空闲的worker。如果workerCount是0,会先把状态变为TIDYING,调用hook方法。最后将状态变为TERMINATED,在一切的最后,唤醒termination的线程。

问题 ?

  1. tryTerminate看起来是和线程池的状态有关系的,但是这里为啥count不是0的时候每次都是终止一个,什么目的?

    从下面的代码中可以看到一共有下面几个地方调用了它。

    • processWorkerExit(线程退出)
    • addWorkerFailed(启动work失败)
    • shutdownNow
    • shutdown
    • remove

    在不同的场景下面都能调用这个方法,说明这个方法兼顾的场景多。

    这方法就是尝试终止线程池,按照判断逻辑一点点的来说

    // 看这里的代码要对照这上面画的图来说
     // 线程池在正常运行
    if (isRunning(c) ||
                    //这意思就是说,线程池已经到垂死挣扎的时刻了,因为TIDYING状态按照上面的图来说,只有满足条件才可以。
                    // 并且TIDYING方法,只有在这个方法里面设置,如果现在的状态比这个大,那就说明可能这个时刻有线程已经操作了。
                    runStateAtLeast(c, TIDYING) ||
                     // 状态等于SHUTDOWN 并且 任务队列不是空的
                     // 这个条件就是调用了shutDown方法,如果队列不是空的,说明还有任务,按照shutdown方法的要求,不能关闭线程池,
                    (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                   // 上面的三种都不是符合条件的,不符合条件的直接返回。
                    return;
              
    

    只要有一个满足,就表示当前的线程池是不能被终止的。

    能满足终止条件的就是stop状态和状态为SHUTDOWN但是队列是空的。

    这两个状态都是可以终止线程池的,shutdownNow方法本就是这个意思,状态为SHUTDOWN但是队列是空的。也符合shutDown方法的意思。

    这里只中断一条我没有看懂。之后在补充补充

     // workCount的数量不是0,这里就只中断一条。
    if (workerCountOf(c) != 0) { 
                    interruptIdleWorkers(ONLY_ONE);
                    return;
                }
    

    后面的就是调用钩子方法了。没有啥意思了

  2. 什么样的情况下,workCount会变为0。

    我能想到的,就是在worker是异常中断的,并且此前只有这一个。在上面的 processWorkerExit方法(减少,–)之后,就会变为0。

 final void tryTerminate() {
    // 一开始就死循环
        for (;;) {
            int c = ctl.get();
           // 如果线程池在正常运行
            if (isRunning(c) ||
                //这意思就是说,线程池已经到垂死挣扎的时刻了,因为TIDYING状态按照上面的图来说,只有满足条件才可以。
                // 并且TIDYING方法,只有在这个方法里面设置,如果现在的状态比这个大,那就说明可能这个时刻有线程已经操作了。
                runStateAtLeast(c, TIDYING) ||
                 // 状态等于SHUTDOWN 并且 任务队列不是空的
                 // 这个条件就是调用了shutDown方法,如果队列不是空的,说明还有任务,按照shutdown方法的要求,不能关闭线程池,
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
               // 上面的三种都不是符合条件的,不符合条件的直接返回。
                return;
          
          // 满足条件的就是STOP状态
          
           // 当前活动的worker不是0,就会中断一条(ONLY_ONE默认为true表示一条,false为全部)空闲的worker。怎么区分是否空闲,只要try lock获取成功就说明是空闲的。
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
           // 到下面说明 现在池中workcount的数量是0.
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
              // 将线程池的状态变为一个中间状态。TIDYING,
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                       // 钩子方法
                        terminated();
                    } finally {
                       //在finally里面将状态变为TERMINATED。
                        ctl.set(ctlOf(TERMINATED, 0));
                       // 唤醒之前等待的线程,(主要就是调用awaitTermination方法的线程)
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }
interruptIdleWorkers方法分析

这里的逻辑很简单了,循环判断,只要worker里面的thread没有中断过,并且worker也可以tryLock成功。就说明当前的worker是空闲的,随即调用线程的interrupt方法

 private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

通过上面的分析,已经知道worker就是一个Runnable,并且worker里面有一个Thread,那么worker是在哪里运行的呢?

详情看下面addWorker章节

addWorker方法分析

创建worker对象,并且启动

通过上面worker类的分析知道,创建worker的时候必须给一个task,作为worker里面的firstTask。

在创建worker之前,还是先来一波状态检查,还有worker数的检查,利用自旋消除锁,创建worker对象,添加到worker集合里面,有必要就替换largestPoolSize,并且启动worker,如果添加失败,就调用addWorkerFailed方法。

问题

  1. 判断线程池状态的逻辑是什么?为什么要这么判断

    再次看一下这一块的判断逻辑

        if (rs >= SHUTDOWN &&
                    ! (rs == SHUTDOWN &&
                       firstTask == null &&
                       ! workQueue.isEmpty()))
                    return false;
    

    运行状态大于等于SHUTDOWN并且rs不是SHUTDOWN或者firstTask不是null或者任务队列是空的。在这样的前提下面就不能创建worker了。

    1. 如果rs小于SHUTDOWN,那只有running状态了,说明线程池现在是一个合格的状态。可以直接添加worker。如果状态是running,判断的第一个条件都不会满足。
    2. rs大于等于SHUTDOWN,说明线程池已经不是正常的状态了,如果这个时候状态不是SHUTDOWN,就说明线程池这个时候已经到清理资源的状态了,肯定不能创建了。
      1. 如果状态是SHUTDOWN,还在提交任务,按照shutdowm方法的作用(不在接受新的任务)。这个时候就不在创建worker来处理新提交的任务了。
      2. 如果这个状态还是SHUTDOWN,提交的任务是null,队列里面是空的。说明就不需要创建新的worker了,如果队列里面不是空的。就可以创建新的worker。也就是说,在调用了shudown方法之后,线程池的状态变为了shutdown,但是此前任务队列里面还有任务,每次提交一个空任务的时候,就像正常的逻辑一样,条件满足就可以创建新的worker。
  2. 在创建worker之后,这里的线程池的状态判断是为了什么?

    再次检查是为了在此期间,有的线程将线程池的状态变为shutdown方法。因为ctl是一个原子引用。里面用volatile修饰,能保证内存的可见性。

  3. 启动失败,为啥不用catch处理,在这里直接用的finally?

    首先,线程异常,jvm会调用UncaughtExceptionHandler#uncaughtException方法,不用catch,可能是为了简单吧。

    因为有标志位,所以,catch里面的处理逻辑就可以放在finally里面,这样看起来简单,大方。

addWorkerFailed方法请看下一章节


private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
           // runState
            int rs = runStateOf(c);
           
           // 检查能否创建worker
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
              //workCount
                int wc = workerCountOf(c);
               //如果workCount 大于 最大容量 或者大于了corePoolSize或者maximumPoolSize(通过core参数指定)
               // 表示不能创建新的worker了
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
               // workCount ++。跳出retry标志位,也就是跳出这个循环体,
              // 如果这次操作失败,说明,同一时刻有线程也在做同样的操作,ctl是原子引用类,里面有
              // volatile修饰的变量,能保证内存的可见性。如果再次读取的状态不一致再次循环,循环的开始点是retry标志位。
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
      
  // 执行到这里,说明workercount已经++成功。
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
           // 创建Worker,将task传递过去,并且在Worker里面,会通过ThreadFactory来创建新的线程。
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
              //获取mainLock
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                   
                  //持有锁的前提下面,再次检查runState,防止在获取锁之后,线程池的状态发生变化
                    int rs = runStateOf(ctl.get());
                      //如果runState是running,(只有running小于SHUTDOWN)
                   // 或者 runState是SHUTDOWN并且firstTask是null,
                   // 这两种情况都是符合要求的。就可以将创建的worker添加到一个集合里面。
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                       // 预检查线程的状态,如果在线程没有启动之前,isAlive方法返回true,就说明出现问题了,还没有启动线程,但这个方法确返回了true。
                        if (t.isAlive()) 
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                       // 如果当前worker集合的个数值,大于largestPoolSize,就赋值替换。
                      // largestPoolSize表示线程池中有过的worker的最大容量。
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                      
                       // 添加线程成功成功。将workerAdded变为true。
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
               // 如果添加成功,就启动。这个时候,worker就开始干活了。
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
           // 如果启动失败。也就是workerStarted是false。
            if (! workerStarted)
               // 就会到下面的这个方法里面,
                addWorkerFailed(w);
        }
   // 返回workerStarted。表示worker是否启动成功。
        return workerStarted;
    }
addWorkerFailed方法分析

处理启动Worker失败,从worker的集合中移除,如果这个worker存在,减少worker的数量,并且调用 `tryTerminate()方法,重新检查终止,防止因为这个worker的存在,而导致不能终止。

注意,这里调用tryTerminate方法

   private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }

说到这里,只是说了线程池里面的一些重要的方法,下面就从常见的execute方法说起。

execute方法分析

从这个方法,可以看出线程池处理任务的大体的步骤

  1. 如果现在运行的线程数小于corePoolSize,尝试启动一个新的worker,将task作为他的firstTask。这个操作是调用addWorker方法(上一章节分析过,他会自动检查runState和workCount,如果不能启动就会返回false)
  2. 上面的条件不满足,task就会入队,如果成功。还会再次检查是否还要添加thread,因为自从上次检查之后,期间在没有检查过,因为第一步和第二部不是原子性的,可能存在在这个期间,线程池的状态变化,或者线程的死亡。还得检查线程池的状态。
  3. 上面的条件不满足,尝试再次调用addWorker方法。如果也失败了,线程池可能关闭了,或者已经到饱和状态了,调用拒绝策略。

问题

  1. 第二步骤的判断有必要吗?

    有,第一步和第二步不是一个原子操作,状态完全有可能在这个期间被修改,并且通过上面的分析,addWorker方法,在结束之后就会启动线程,完全有可能在这个期间,线程也出现了问题。我突然想到了一个问题,在线程池运行的时候如果一个线程异常了,就会将这个线程先清除掉,然后创建一个新的线程。那么有没有可能这两个的值在同一时刻读取的是0,然后同时操作。不可能,因为ctl是原子整形类。

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // 上面说的第一步
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
           // 如果添加失败,再次获取c,确保是最新的。
            c = ctl.get();
        }
    // 第二步。
    // 判断状态,入队,入队成功后,判断是否还要在添加thread。
        if (isRunning(c) && workQueue.offer(command)) {
           //再次检查
            int recheck = ctl.get();
           // 如果线程池的状态不是running,并且从任务队列移除task成功,调用拒绝策略。
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果workCount的数量为0,重新创建一个新的worker。fistTask是null,这个worker也不是核心线程。
           // 如果在核心线程满了之后,因为线程池的状态不是running,并且队列不在任务队列中,此时,workcount突然变为0,就会创建一个非核心线程。或者,线程池的状态是running,但是workcount变为了0,也会创建一个非核心线程。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
       // 创建非核心线程失败。对应第三部。
        else if (!addWorker(command, false))
            reject(command);
    }
remove方法分析

注意,这里也调用了tryTerminate方法,这方法只要就是将task从任务队列中移除,并且调用 tryTerminate 方法

  public boolean remove(Runnable task) {
        boolean removed = workQueue.remove(task);
        tryTerminate(); // In case SHUTDOWN and now empty
        return removed;
    }
reject方法分析

调用拒绝策略

 final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

shutdown方法分析

关闭线程池,之前提交的任务还在运行,但是不会创建处理新提交的任务

启动有序关闭,其中执行先前提交的任务,但不会接受新任务。 如果已经关闭,调用没有额外的效果。

注意,这里也调用了tryTerminate方法

注意,这里也调用了

说明:

  1. interruptIdleWorkers方法前面已经介绍过了,这里不在累赘说
  2. 这个方法将线程池的状态变为SHUTDOWN,在想想之前的addWorker方法这对于SHUTDOWN状态的判断,将两个联想起来就很容易理解
   public void shutdown() {
      // 获取mainLock
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 遍历检查权限
            checkShutdownAccess();
           //将线程池的状态变为SHUTDOWN,
            advanceRunState(SHUTDOWN);
           //中断空闲的线程
            interruptIdleWorkers();
           // 留给ScheduledThreadPoolExecutor的hook方法
            onShutdown(); 
        } finally {
            mainLock.unlock();
        }
     // 再次调用了tryTerminate方法
        tryTerminate();
    }

shutdownNow方法分析

立刻关闭线程池,并且中断所有正在执行的worker,将队列中没有执行的task返回

说明:

  1. 这里也得和addWorker方法关联起来,才好理解。重点就是里面对于线程池状态的判断。

注意,这里也调用了tryTerminate方法

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
       // 还是一样的步骤,只不过这里的状态直接变为STOP了
        checkShutdownAccess();
        advanceRunState(STOP);
        // 中断所有的worker
        interruptWorkers();
       // 将队列中现存的task返回
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
   // 调用 tryTerminate方法
    tryTerminate();
    return tasks;
}
drainQueue方法分析

调用queue的drainto方法,这个方法会将所有可用的元素从queue中移除,并且添加到taskList里面。

  private List<Runnable> drainQueue() {
        BlockingQueue<Runnable> q = workQueue;
        ArrayList<Runnable> taskList = new ArrayList<Runnable>();
        q.drainTo(taskList);
        if (!q.isEmpty()) {
            for (Runnable r : q.toArray(new Runnable[0])) {
                if (q.remove(r))
                    taskList.add(r);
            }
        }
        return taskList;
    }

awaitTermination方法分析

在线程池完全终止之后,调用才能从这个方法返回,也就是说,调用这个方法之后,会一直等待线程池中的任务执行结束,状态变为TERMINATED

先获取mainLock,判断当前线程池的状态是否是TERMINATED,如果是就直接返回,否则就等待,等待在termination里面,termination是mainLock的Condition。在tryTerminate方法里面会再次唤醒。(当调用完了terminated方法之后,在finally里面状态变为TERMINATED,workCount变为0,之后,就会唤醒termination

注意,这里也和tryTerminate方法有关系

  public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
   

下面看几个set方法,这些方法还挺有意思的,可以动态的调整线程池的大小

setCorePoolSize方法分析

设置corePoolSize

这个方法比下面的setMaximumPoolSize方法复杂,因为线程池的机制就是要保证corePoolSize数量的线程数。

corePoolSize分为两种情况,

  1. 比原来的小

    中断空闲的workerCount。

  2. 比原来的大

    在相差多少和queue的size中选择一个最小的,循环添加。

问题?

  1. 如果设置的corePoolSize比之前的小,中断空闲线程的时候没有空闲的线程,那这次设置corePoolSize是不是无效?

    不是,中断的时候虽然没有空闲的线程,但是修改了corePoolSize,超过的那部分线程,就变为了非核心线程,在getTask方法中,会有对应的TTL(keepAliveTime)起作用。

  2. Math.min(delta, workQueue.size()) 这段代码有必要吗?为啥不能直接创建delta个线程

    因为这个时候不能确定,到底要创建多少个线程,因为,如果直接创建delta个线程,结果队列中有小于delta个任务,多创建的线程不就浪费了吗?所以,这里取两个的最小值。当队列没有值的时候,就不需要创建了。

  public void setCorePoolSize(int corePoolSize) {
        if (corePoolSize < 0)
            throw new IllegalArgumentException();
        // 
        int delta = corePoolSize - this.corePoolSize;
        this.corePoolSize = corePoolSize;
        // 如果当前的的workCount 大于 要设置的corePooSize,就会中断空闲的worker,
        if (workerCountOf(ctl.get()) > corePoolSize)
            interruptIdleWorkers();
    
       // 大于0 说明这次设置的corePoolSize比上一次的大
        else if (delta > 0) {
            // 从队列和delta中选择一个最小的。这样做是为了什么
            int k = Math.min(delta, workQueue.size());
           // 一直添加
            while (k-- > 0 && addWorker(null, true)) {
                if (workQueue.isEmpty())
                    break;
            }
        }
    }

setMaximumPoolSize方法分析

设置MaximumPoolSize大小

先赋值,如果超过就中断空闲的线程。

问题

  1. 还是和上面同样的问题,如果没有空闲的线程,那么线程池中线程数还一直是之前的数量吗?

    不是,和上面的逻辑一样,在getTask方法里面会有TTL(KeepAliveTime)

设置

   public void setMaximumPoolSize(int maximumPoolSize) {
        if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
            throw new IllegalArgumentException();
        this.maximumPoolSize = maximumPoolSize;
        if (workerCountOf(ctl.get()) > maximumPoolSize)
            interruptIdleWorkers();
    }

setKeepAliveTime方法分析

设置KeepAliveTime

public void setKeepAliveTime(long time, TimeUnit unit) {
    if (time < 0)
        throw new IllegalArgumentException();
    if (time == 0 && allowsCoreThreadTimeOut())
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    long keepAliveTime = unit.toNanos(time);
    long delta = keepAliveTime - this.keepAliveTime;
    this.keepAliveTime = keepAliveTime;
    if (delta < 0)
        interruptIdleWorkers();
}

那看完这些,线程池中的线程如果出现了异常会怎么办?看完这篇文章,应该有答案了(答案在processWorkerExit方法里面)

关于ThreadPoolExecutor的分析就分析到这里了。 如有不正确的地方,欢迎指出。谢谢。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值