线程池:从execute开始

899 阅读7分钟

ThreadPoolExecutor中通过isRunning方法检查线程池状态:

//原子操作类,防止并发,前三位存储线程池状态,剩余位数表示线程池中线程数量
AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

//判断线程池是否是运行状态
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}
// 代表线程池正在运行    
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;

execute

通过调用ExecutorServic的submit和execute方法,可以添加一个任务到线程池中。至于这两个方法的区别在于submit方法添加的任务会返回任务的执行结果,execute方法没有返回结果。在submit方法中仍然是调用execute方法来添加任务。

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // workerCountOf(c)用于计算线程数量,并判断是否小于核心线程数量
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //执行到这里说明当前线程数>=核心线程数,接着判断线程池状态,若是Running则将任务入队
        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);
    }

小结一下execute方法的执行步骤:

  1. 首先判断正在运行的线程数是否小于核心线程数,少于的话会调用addWorker方法来创建一个核心线程来执行当前任务。
  2. 当前正在执行的线程数>=核心线程数,若当前线程池状态为正在运行,则执行入队操作,将任务添加到队列中。为了防止并发,再次判断线程池状态,如果线程池状态异常,则删除刚才入队的任务,并执行饱和策略。如果线程池状态为正在运行且线程数为0,则创建一个临时线程,也就是非核心线程执行任务。
  3. 执行到这里,表明当前线程数量>=核心线程数,且入队失败,会再次调用addWorker方法创建一个非核心线程来执行任务,创建失败则执行饱和策略。

addWorker

通过对execute方法的分析,我们可以看到具体的线程创建与复用机制应该在addWorker方法中。

addWorker方法中接收两个参数,firstTask表示接收的第一个任务,core表示是否创建核心线程。

private boolean addWorker(Runnable firstTask, boolean core) {
 ......
 boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //worker对象的作用类似于具体的执行单元
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                //获得锁,注意mainLock为非公平锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());
                    //只有当线程池处于Running状态,才会继续往下执行
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        //如果Worker对象中的线程已经在执行了,抛出一个异常给他
                        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 {
            //线程启动失败的话,删除当前worker对象
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;

上述省略了部分代码,首先会根据任务创建一个Worker对象,然后获得Worker内部的Thread对象,后面会调用Thread的start方法来执行线程。线程的创建和任务的执行并没有具体的细节体现,可以预料到在Worker内部,那么具体的细节进入Worker的内部看一看:

 private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }    
 }
 

Worker类实现了Runnable接口和AQS锁,在Worker的构造函数中会保存传递见来的任务,然后调用线程工厂创建一个线程。我们注意到Worker类实现了Runnable接口,并且在创建线程时将当前Worker对象传递给Thread。所以当调用hread的start方法启动线程时,回调的是Worker的run方法。

// run方法中委托runWorker方法来继续执行
public void run() {
            runWorker(this);
}
// 
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //获取当前Worker保存的任务 
        Runnable task = w.firstTask; 
        w.firstTask = null; // firstTask置null,方便gc,因为Worker对象存活时间较长
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                //如果线程池时Stop状态,那么要确保当前线程为中断状态
                //如果当前线程处于Running或SHUTDOWN状态,确保当前线程没有设置中断状态
                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对象的run方法中会调用runWorker方法,在runWorker方法中存在一个while循环,首先执行Worker中保存的任务(也就是创建Worker对象时的第一次任务),若该任务为null,则会调用ThreadPoolExecutor的getTask方法从阻塞队列中获取任务执行,任务执行完毕会循环调用getTask方法获取任务执行,所以说线程池中复用的并不是Thread,只是在循环的获取任务的Runnable对象并执行。

getTask

线程的复用原理在于getTask方法中任务的获取,根据线程池状态、队列大小和是否被超时策略影响来决定是否返回null值,结束线程。

 private Runnable getTask() {
        //队列中获取任务超时时,会返回null,并重置timedOut = true
        boolean timedOut = false; 
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            //检查线程池状态,分为两种情况,返回null
            //1. 线程池状态为SHUTDOWN,且队列为空
            //2. 线程池状态为STOP
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // timed表示超时策略,allowCoreThreadTimeOut表示核心线程是否允许超时设置
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            
            //1. 如果当前线程数>最大线程数,或者当前Worker被超时策略控制
            //2. 如果线程池中存在线程,或者队列为null,
            //同时满足以上1,2条件的话,返回null,线程被结束
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
               //poll方法从队列中取出一个任务,取不到则挂起当前线程keepAliveTime时间,超出指定时间仍取不到,返回null
               //take方法从队列中取出一个任务取不到时,会一直挂起
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                //任务不为null,返回    
                if (r != null)
                    return r;
                //超时,还没取到,继续执行for循环,最终会执行到上述的if方法内    
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

小结一下getTask方法中的具体做了什么:

  1. 检查线程池状态,如果线程池状态为SHUTDOWN,且队列为空,或者程池状态为STOP,则返回null值结束线程。
  2. 控制线程的超时策略时,可以设置allowCoreThreadTimeOut=true,修改核心线程受超时策略的控制,默认为false。
  3. 如果当前Worker被超时策略控制且队列为null,会返回null,结束当前线程。上述if中wc > 1是因为,要加上当前Worker对象所在的线程。
  4. 核心线程在未设置allowCoreThreadTimeOut=true的前提下,会指定take方法获取任务,超时等待。其他线程调用poll方法获取任务,超时指定时间,会走到步骤3中,最终返回null,结束线程。

总结

  1. 当调用线程池的execute方法时,首先会判断当前正在执行的线程数是否小于核心线程树,若小于,则调用addWorker方法创建一个Worker对象执行当前任务,若是核心线程数已满,则会将任务加入阻塞队列,加入阻塞队列失败的情况下会创建非核心线程来执行任务,也是调用addWorker方法,如果还是失败的话执行饱和策略。
  2. addWorker方法中会委托Worker对象的runWorker方法来执行具体的任务,内部执行一个while循环调用getTask方法获取任务执行,当获取任务为null时,结束线程。
  3. getTask方法中维护一个for循环,内部根据线程池状态,队列大小和当前线程是否会被执行超时策略,来获取任务,核心线程默认不执行超时策略,会一直等待获取任务,可以通过设置allowCoreThreadTimeOut,来强行设置超时策略,非核心线程在超时获取任务为null,且队列为null的情况下,会返回null,结束线程。