面试|ThreadPoolExecutor中的线程如何从等待队列获取任务

1.问题引出

我们知道一个线程创建的时候就会附带一个Runnable任务,如果该Runable任务执行完毕后,该线程如何从线程池的等待队列中获取一个任务呢?

上一篇博客讲到线程池中线程和该线程的第一个任务封装在一个Worker类中,这个Worker类本身实现了Runnable接口,源码定义:

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);
	}
	
	public void run() {
	        runWorker(this);
	}
}

通过构造函数可以知道,创建Worker对象就会创建一个线程,但是线程传递的参数是this,即该Worker对象,会不会感到很奇怪?!为什么不直接把firstTask传递给新创建的线程呢?!这是一个很关键的点,如果如我们所愿,firstTask任务直接传递给新线程,那么当firstTask执行完之后,该线程如何获取队列中的任务呢?或许你有更好的方法,但这里采用下面这种思想:把去等待队列中获取任务的过程封装成一个Runnable任务(就是该Worker对象),新创建的线程启动后,就会执行该Runnable任务,该Runnable任务执行完firstTask任务后,就会不断的从等待队列中获取任务,直到等待队列为空,该线程才会销毁

下面详细分析这个过程,主要体现在runWorker(Worker)和getTask()方法内。

2.runWorker(Worker)

Worker对象,即Runnable任务的run()方法执行runWorker(this)方法:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread(); // 获取当前线程
        Runnable task = w.firstTask;  // 获取传递的首个任务
        w.firstTask = null;  // 获取后把firstTask属性置空
        w.unlock(); // allow interrupts 允许中断
        boolean completedAbruptly = true;
        try {
            // getTask()获取等待队列中的任务
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // 当线程池状态为STOP,则中断该线程
                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; // 如果任务执行没有发生异常,则赋值false
        } finally {
			// 最后判断线程是否保留
            processWorkerExit(w, completedAbruptly);
        }
    }

这里需要注意异常的处理;可以看到有三重捕获,分别是RuntimeException、Error和Throwable,Throwable是异常体系的父类,这表明线程执行的任何异常都会被捕获;同时,还提供afterExecute(Runnable,Throwable)钩子方法处理抛出的异常,这里默认没有任何实现,可以定制处理逻辑。
注意:一旦task.run()抛出异常,在throw异常之前会执行三重finally代码块里的逻辑;这样设计的目的在于即使任务执行出现异常,也能保证线程池的线程不会异常中断,而是按照正常流程走。

3.getTask()

getTask()方法是从等待队列获取任务的核心过程。

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        // 自循环保证池状态的一致性
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 如果等待队列为空,则工作线程数减1,并返回null;注意该线程是否被销毁的逻辑在runWorker(Worker)方法内,此处不用处理
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);  // 获取工作线程数

            // 工作线程是否被销毁?
			// 如果allowCoreThreadTimeOut设置为false,表明当该线程不是核心线程时就会被销毁,否则核心线程不会被销毁;
			// 如果allowCoreThreadTimeOut设置为true,表明即使是核心线程也会被销毁;
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

			/**
			** 如果工作线程数大于maximumPoolSize且工作线程数大于1或者等待队列为空,则工作线程数减1且返回null
**/
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
				//如果timed为true,则执行poll(Long,TimeUnit),否则执行take()
				// poll(Long,TimeUnit)会限制线程等待指定时间,如果等待超时则返回null;take()则不会限制等待时间,阻塞直到返回一个任务;
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;  // 等待超时了
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

获取等待队列中的任务,除了考虑线程池的状态,还需要考虑allowCoreThreadTimeOut值得设置以及线程池内线程数量情况。

4.总结

这篇主要学习ThreadPoolExecutor处理线程获取等待队列中得问题。涉及runWorker(Worker)和getTask()两个方法,两个方法内容很简单,但需要考虑到线程池的状态以及线程池内线程数量,因此显得复杂;如果我们理清了方法的逻辑以及前面几篇关于ThreadPoolExecutor的讲解,相信理解这里面的方法并不难。

你可能感兴趣的:(Java基础知识,Java基础面试题)