Java之ThreadPool

该文是本人的学习总结,若有错误,望指正;下文所有分析都基于JDK-8

本文不做源代码的分析,因为有大量的文章已经这样做了,并且做得很漂亮,比如这篇。这里只是想大概梳理一下ThreadPool设计的思路。

ThreadPool的状态参数

JDK的线程池定义了五个状态参数,如下代码所示;

//正常状态:处理任务并接收新的任务
private static final int RUNNING    = -1 << COUNT_BITS;

//关闭状态:处理BlockingQueue中的任务,但不再接收新的任务
//若有新的任务继续到来,如何处理?请看下文
private static final int SHUTDOWN   =  0 << COUNT_BITS;

//停止状态:不再接收新任务,也不再处理BlockingQueue中的任务;
//不仅如此,还会中断正在进行中的任务
private static final int STOP       =  1 << COUNT_BITS;

//姑且称为整理状态吧,这个状态的ThreadPool会调用钩子方法 terminated()
private static final int TIDYING    =  2 << COUNT_BITS;

//结束状态:terminated()方法完成后就是这个状态了
private static final int TERMINATED =  3 << COUNT_BITS;

需要注意的是,这些值是多少不重要,重要的是它们的相对大小;因为程序中是使用它们的相对大小来进行逻辑判断;采用这种移位来进行定义,只是便于程序利用位运算来进行计算;

初始化一个ThreadPool时,它的状态就是 RUNNING 的;

上面实际上就是一个状态机,那么这些状态如何进行转移?

  • RUNNING -> SHUTDOWN:调用shutdown(), 很熟悉的方法;
  • (RUNNING or SHUTDOWN) -> STOP:调用shutdownNow();
  • SHUTDOWN -> TIDYING:不是主动的触发,而是当BlockingQueue和线程池里线程数都为0的时候,可以把此时的状态记为TIDYING;
  • STOP -> TIDYING:线程池里没有线程了,记状态为TIDYING;
  • TIDYING -> TERMINATED:terminated()结束;

线程的生命周期

我不想知道线程是怎么来的,只想知道线程是怎么没的

日常中,我们都使用线程池来执行任务。但是作为计算机工作者,追根溯源应该是我们最起码的一个学习态度,^-^。

当有新的任务(Task)被提交(execute)到线程池里时,会被包装到Worker类里。Worker里重要的属性如下:

//真正执行任务的线程
final Thread thread;

//待执行的任务,可为空
Runnable firstTask;

新建线程

线程池会根据coolPoolSize、maximumPoolSize和BlockingQueue中任务的数量来决定是否新建线程;刚开始的时候肯定是要新建线程的,每新建一个线程(使用ThreadFactory创建),实际就是new 一个Worker,并把新的线程放在其属性thread中;

线程的保活

之所以使用线程池,是因为线程的创建,管理等比较麻烦,交给线程池来做,一劳永逸;那就有一个问题了,线程池中的部分线程如何保证是一直存活的?这样才能高效得服务使用者。

线程池中的线程和我们自己创建的线程一个较大的区别在于:当线程池中的线程运行完一个任务后,不是被立刻回收(结束)了,而是去BlockingQueue中获取任务,获取到新的任务,就执行这个任务;获取不到,才干掉或者阻塞(blocked)线程阻塞实际上就是线程保活的一个重要手段(这里可以参考线程池的getTask()方法);熟悉BlockingQueue原理的朋友,肯定知晓阻塞的线程实际上是被park了,若queue非空,就会unpark这些线程。

线程的销毁

闲暇时,线程池中只会保留coolPoolSize的线程,多余的线程都会被干掉;当然,线程池被shutdown时,所有线程都会被干掉的;

举个例子,coolPoolSize=5,maximumPoolSize=10,BlockingQueue的capacity=20;当任务很多时,池里的线程很可能会扩充到10;那么任务执行完后,如何干掉多余的5个?这里要参考runWorker()方法了, 下述代码省略了一些;

final void runWorker(Worker w) {
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock(); //获取锁
                beforeExecute(); //钩子
                task.run();
                afterExecute();//钩子
                w.unlock();
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
}

while循环不结束,当前线程就无法结束;getTask()会根据线程池的设置返回null或task;返回null,while结束,线程可以回收;当getTask()判断线程数已经不大于coolPoolSize,那么这些剩余的线程如上文所述,就会阻塞。

几个问题

  • 线程池刚诞生时就会创建 coolPoolSize个线程吗?
    :不是。而是随着任务的到来,逐渐新建线程;

  • 内部类Worker为何设计为一个锁,并且是不可重入的?
    :线程池暴露了一些设置参数的方法,比如setCorePoolSize()等;这些参数会影响到一些线程的生命周期;不妨看一下setCorePoolSize()的方法体:

public void setCorePoolSize(int corePoolSize) {
        ......
        if (workerCountOf(ctl.get()) > corePoolSize)
            interruptIdleWorkers();
        else if (delta > 0) {
            .......
            while (k-- > 0 && addWorker(null, true)) {
                if (workQueue.isEmpty())
                    break;
            }
        }
    }
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();
        }
    }

个人理解,线程池中的线程要么在运行,要么在阻塞;当一个线程在运行时,尽量不要中断这个线程;所以这里的非重入锁是为了禁止中断运行中的线程而设计,尽管目前运行的线程根本就没有响应这个中断;假如不是一个锁,或者是一个可重入的锁,那么interrupt()极有可能中断一个正在运行的线程(这个线程的中断标志位会被set),本人觉得这样不会影响线程的运行,但是按照java api doc描述,除非线程池即将停止,否则不要给正常的线程设置中断标志位;

Before running any task, the lock is acquired to prevent other pool interrupts
while the task is executing, and then we ensure that unless pool is stopping, 
this thread does not have its interrupt set.

本文参考了 这里

你可能感兴趣的:(Java之ThreadPool)