ThreadPoolExecutor线程池源码解析

简单应用示例

创建20个任务,然后放入固定线程池进行执行,下面的原理阐述主要根据newFixedThreadPool进行分析

public static void main(String args[]){
    ExecutorService executorService = Executors.newFixedThreadPool(2) ;
    for(int i = 0; i < 20; i++){
        final int result = i ;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "-" + "运行中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-" + "结果:" + result);
            }
        });


    }
    executorService.shutdown();
}

构造函数

所有的构造函数都调用一个底层的构造函数,如下:

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,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

参数的含义,我们就不说了,网上有的是。主要对细节进行解析和说明,根据newFixedThreadPool的构造方法参数进行分析,首先看Executors.defaultThreadFactory工厂。

线程工厂

大家都熟知线程池的线程是由工厂创建的,但是需要搞清楚以下几点:

  • 工厂是如何实现的
  • 工厂是如何创建线程的
  • 线程池是如何实现工厂模式的
    Executors.defaultThreadFactory是固定线程池的默认工厂,我们可以跟进查看,发现如下代码:
public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}
static class DefaultThreadFactory implements ThreadFactory {。。。。}
public interface ThreadFactory {
    Thread newThread(Runnable r);
}

其中默认的工厂是实现了ThreadFactory,ThreadFactory中只有一个方法,就是创建线程的方法,需要传递进去一个任务(Runnable)。我们根据默认的工厂DefaultThreadFactory进行分析,首先进入默认工厂的实现代码:

static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

代码相对简单,定义两个原子性递增的变量来标志当前线程池编号和当前线程编号,然后创建线程并设置非守护线程和普通优先权。下面直接进入重点方法execute和submit,在里面可以看到其他关键参数的作用和具体细节的操作。

Worker对象

Worker对象是线程池对线程和任务的封装,具体的任务和任务的执行都是由Worker对象管理,里面存储任务和线程,源码如下:

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);
    }
    //...
}

可以看到Worker实现了Runnable接口并继承了AQS,初始化的时候,也是把当前的Worker对象放入Thread中,这样worker的run方法就得以执行

重点执行类方法

execute方法

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 = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

我们以最开始的例子进行讲解,核心线程数是2,循环添加20个任务到线程池,对应此处源码进行说明如下:

  • 如果工作线程数少于核心线程数,创建线程运行任务
  • 如果工作线程数量大于等于核心线程数,则压入我们的阻塞队列,固定线程池默认队列LinkedBlockingQueue
  • double check,如果压入队列过程中,线程池变为shutdown状态,会导致任务不会执行也不会执行拒绝策略。此处检查:如果线程池是非运行状态,则从队列移除任务并执行拒绝策略。
  • 如果线程池是运行状态,而工作线程数量此时为0,则创建线程运行
  • 如果创建运行任务失败,则执行拒绝策略
    上面的说明还是比较笼统,下面我们对细节进行分析,首先看addworker方法,先说明一下参数含义:
  • firstTask 线程首先应该执行的任务,如果没有则传入null
  • core 是否创建核心线程,true创建核心线程,false创建非核心线程,其实传递什么没什么太大含义,就是用哪个线程数量做判断而已。
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            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
        }
    }


    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
  • 进入无限循环,判断状态,(如果当前状态大于shutdown状态) 并且(如果当前状态是shutdown,队列中不存在任务)不可创建线程。
  • 如果可以创建线程,进入内部的无限循环,根据线程创建的类型(核心或非核心),来确定是否超出指定数量
  • 然后原子性递增线程数量,如果成功则跳出最外层循环;失败,则判断线程池状态是否发生改变,如果改变,跳转到外层死循环,没改变,继续内部死循环。
  • 创建Worker对象,Worker对象内通过我们指定的线程工厂DefaultThreadFactory创建Thread线程。
  • 然后将Worker对象放入HashSet的列表中,此处由独占锁ReentrantLock保证线程安全。
  • 然后调用start启动线程(其实是启动Worker任务,Worker实现了Runnable,创建线程是时候,指定当前Worker对象传入Thread中,也就是会调用Worker内的run方法)。
  • 如果启动线程失败,调用addWorkerFailed方法,后续说。

runworker方法

具体的实现全部在runWorker方法中,首先翻译一下runWorker的注释(百度翻译自行理解):

1.我们可以从一个初始任务开始,在这种情况下,我们不需要得到第一个任务。否则,只要池正在运行,我们就可以从getTask获取任务。如果它返回null,那么工作进程将由于池状态或配置参数的更改而退出。其他退出是由外部代码中的异常抛出导致的,在这种情况下completedAbruptly保持不变,这通常会导致processWorkerExit替换此线程。
2.在运行任何任务之前,获取锁以防止任务执行时其他池中断,然后我们确保除非池停止,否则该线程没有其中断集。
3.每个任务运行之前都会有一个beforeExecute调用,该调用可能会引发异常,在这种情况下,我们会导致线程在不处理任务的情况下死亡(使用completedAbruptly true中断循环)。
4.假设beforeExecute正常完成,我们运行任务,收集其抛出的任何异常以发送到afterExecute。我们分别处理RuntimeExceptionError(这两个规范都保证我们可以捕获)和任意抛出。因为我们不能在Runnable.run中重新抛出抛出的内容,所以我们会在退出时将它们包装在错误中(到线程的UncaughtExceptionHandler)。任何抛出的异常也会保守地导致线程死亡。
5.在task.run完成后,我们调用afterExecute,它也可能引发异常,这也会导致线程死亡。根据JLS第14.20节,即使task.run抛出,此异常也将生效。
异常机制的净效果是,afterExecute和线程的UncaughtExceptionHandler具有我们所能提供的关于用户代码遇到的任何问题的准确信息。
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                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 {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}
  • 获取Worker对象中的task
  • 设置unlock允许中断(这里后续有用,记住)
  • 通过getTask方法循环获取队列中的任务
  • 上独占锁后,调用task.run方法执行任务
  • 运行完毕,解锁,最后调用processWorkerExit方法

getTask方法

主要作用是从阻塞队列中获取任务

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?


    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);


        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }


        int wc = workerCountOf(c);


        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;


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


        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

根据当前的配置设置,对任务执行阻塞或定时等待,如果由于以下任何原因必须退出此工作进程,则返回null:

  • 有超过maximumPoolSize个工作线程。
  • 线程池停止。
  • 线程池已关闭,队列为空。
  • 此工作线程在等待任务时超时,超时工作线程在超时等待前后都会终止,如果队列非空,则此工作线程不是池中的最后一个线程。
  • 如果允许核心线程超时或者线程数量已经大于最大线程数,则使用BlockingQueue的poll(非阻塞)方法限时获取元素,否则take阻塞获取。

processWorkerExist方法

主要作用是递减workercount数量、增加任务完成数、尝试关闭线程池、是否创建新的线程执行剩余任务。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();


    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}
  • 如果comletedAbruptly=ture,代表任务执行发生异常并退出,此时减去workercount数量
  • 上独占锁,增加任务完成总数,从workerset中移除当前worker对象
  • 尝试关闭线程池
  • 如果当前线程池小于STOP状态并且任务的执行没有发生异常,则开始判断allowCoreThreadTimeOut,若为true,则线程数量为0,flase则为核心线程数。如果为0,但是当前队列不是空,则至少需要创建一个线程去执行剩余的任务,如果当前存活线程大于等于min,则直接返回
  • 如果当前线程池小于STOP状态并且任务的执行发生异常,则调用addWorker继续创建线程,执行剩余的任务,也就是常说的worker替换

线程池执行重点总结

根据一下问题点进行总结:

  • 线程是如何创建的?
    线程通过ThreadFactory接口的实现类进行创建,实现类实现newThread方法,方法中使用new Thread构造线程
  • 线程的execute方法执行流程
    工作线程小于核心数量,则创建核心线程,调用addWorker创建Worker对象
    工作线程大于核心数量,则创建非核心线程,并且压入阻塞队列
    压入阻塞队列成功,执行doublecheck检查线程池是否处于运行状态
    如果非运行状态,则从队列中移除该任务并且执行拒绝策略
    如果是运行状态并且发现工作线程数量为0,则创建一个新的线程去队列获取任务并执行。
    工作线程大于核心数量,并且队列添加失败,则执行拒绝策略
  • Worker对象的作用
    Worker对象是对线程和任务的封装,具体的任务执行是在Worker对象中,Worker对象实现了Runnable接口继承AQS,也就是能够以任务的方式运行,线程池通过Worker来实现对线程池的管理
  • Worker对象为什么继承AQS?
    Worker执行前禁止中断,Worker类的state默认-1,如果不调用提前调用一次unlock方法将永远获取不到锁,如果获取到锁,就是可中断的,没获取到锁,不可中断(具体可以看Worker类中的interruptIfStarted方法就知道了,这个方法会在shutdown时调用)。
  • processWorkerExist方法作用
  •       每次worker执行完毕之后,都是调用这个方法来判断当前线程池是否可关闭,并判断队列中是否为空,是否需要新建线程去执行,注意所有线程的新建都是通过Worker对象来构造的。
    
  • runWorker方法
    runWoker方法是任务具体的执行方法和从阻塞队列中获取任务执行,Worker对象的run方法调用的就是runWorker,runWorker中调用的是我们真正任务的run方法,注意此处不是start,是run方法。

线程回收

​关于线程的回收,我们可以通过上述的getTask和processExistWorker进行说明,这里截取重点代码部分

final void runWorker(Worker w) {
    try {
        while (task != null || (task = getTask()) != null) {
           //...
        }
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    }
}

关注上述的worker.remove(w)代码,此处是移除此worker对象,也就是移除此线程,当然还会存在一些状态字段的变更以便线程池能及时获取当前工作的线程数。那么移除此对象之后,等待GC进行回收,这里就是真正回收线程的地方,下面我们看下流程。

  • runWorker方法去调用getTask方法,如果getTask方法返回空,则执行processExistWorker方法,也就是回收线程
  • getTask何时返回空?工作队列为空、线程池已经关闭、线程数大于最大线程数、允许核心线程销毁(allowCoreThreadTimeOut)、从队列获取任务超时(keepAliveTime),这几种情况会触发到processExistWorker方法。

线程池关闭

shutdown

shutdown方法启动有序关闭,不接受新的任务,等待正在执行的任务执行完成,将要开始执行的任务被中断。重复调用没有副作用。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

主要查看interruptIdleWorkers方法,此方法是关闭线程池的主要方法,关闭的原理是中断线程,代码如下:

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集合中的每一个worker,如果当前的worker没有被中断,尝试获取worker锁(worker已经实现了AQS),如果获取锁成功,则中断线程
  • 这里涉及到一个细节,就是worker正在执行的时候已经获取到了worker的锁,所以调用中断的时候,获取不到锁,就不能中断运行中的任务。如果任务还没有执行,查看上面的runWorker方法有个unlock方法调用,如果unlock刚刚执行完毕,则shutdown就可以获取到worker的锁进行中断。
    再看tryTerminate方法,此方法是尝试关闭线程池,细节判断不描述,主要是设置线程池的最终状态,设置为TERMINATED

showdownNow

shutdownNow方法会直接停止worker的执行不会等待执行完毕,也是通过中断来停止。和shutdown不同的是,设置的状态为STOP,shutdown设置的状态是SHUTDOWN

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}
void interruptIfStarted() {
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}
  • 主要关键点interrputIfStarted方法,这个方法被定义在Worker类中,代表的含义是如果worker开始运行则可以中断,shutdownNow的和shutdown不同之处就在这里,shutdown需要获取worker的锁,而shutdownNow不用。
  • 同样调用tryTerminate方法来设置线程池的最终状态。

线程回收和关闭总结

  • 空闲线程是如何回收的?
    • 通过processExistWorker方法进行回收,回收是根据getTask队列中没有任务或者获取队列元素超过keepAliveTime参数值来判断的
    • 然后从workerSet工作集中删除此工作worker,等待GC回收
  • 线程池内存在核心和非核心线程的标志么?
    • 不存在
    • 可以通过addWorker的参数指定来创建核心和非核心线程,但是内部没有具体的区分
    • 回收线程是随机回收,没有区分核心和非核心,保证核心线程数量即可
  • 核心线程数量是否可以回收
    • 可以,通过设置allowCoreThreadTimeOut来判断
    • 如果真,则线程池内所有线程都可以被回收
    • 如果假,保留核心线程数量,只回收核心线程数之外的线程
  • shutdown线程池关闭会有任务没有执行么?
    • 会,有的任务刚刚开始要执行,恰好shutdown获取到了worker锁
  • shutdown会终止运行中的任务么?
    • 不会,因为worker在执行过程中已经获取到了worker的锁,shutdown想要获取被阻塞
  • shutdownNow会终止运行中的任务么?
    • 会,shutdownNow直接调用的Worker内interrputIfStarted方法,不需要获取worker的锁
  • shutdownNow没有执行的任务怎么办?
    • shutdownNow方法存在返回值,没有运行的任务会从队列中取出作为返回,并清空阻塞队列。
  • shutdownNow终止运行中任务怎么办?
    • 凉拌,因为worker执行的时候已经从阻塞队列中取出任务,所以shutdownNow不能返回这种任务了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值