【深入理解 线程池】

深入理解 线程池

  • 介绍
  • 源码学习
    • 线程池的类继承体系
    • ThreadPoolExector
      • 核心数据结构
      • 核心配置参数
        • 线程池的执行流程如图:
      • 线程池的优雅关闭
        • 线程池的生命周期
        • 正确关闭线程池的步骤
      • 任务的提交过程分析
      • 任务的执行过程
        • shutdonw() 与任务执行过程综合分析
        • shutdonwNow() 与任务执行过程综合分析
  • 总结

介绍

线程池(Thread Pool) 把一个或多个线程通过统一的方式进行调度和重复使用的技术(采用池化思想)。
优点:
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性

源码学习

线程池的类继承体系

【深入理解 线程池】_第1张图片
在类图中,有两个核心的类。ThreadPoolExectorScheduledThreadPoolExecutor。后者不仅可以执行某个任务,还可以周期性地执行任务。向线程池中提交的每个任务,都必须实现Runnable接口。通过最上面的Executor 接口中的execute(Ruunable task)向线程池提交任务。同时,在ExecutorService中,定义了线程池的关闭接口shutdown。还定义了可以有返回值的任务。也就是Callable

ThreadPoolExector

核心数据结构

public class ThreadPoolExecutor extends AbstractExecutorService {
     //状态变量
     private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
     //存放任务的阻塞队列
     private final BlockingQueue<Runnable> workQueue;
     //对线程池内部各种变量进行互斥访问控制
     private final ReentrantLock mainLock = new ReentrantLock();
     private final Condition termination = mainLock.newCondition();
     //线程集合
     private final HashSet<Worker> workers = new HashSet<Worker>();
       ......
       }

每一个线程是一个Worker对象。Worker继承自AQS。具有锁的一些特性,对于完成线程池的关闭和执行任务起到关键作用。

 private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        //Worker 封装的线程
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        // Worker 接受到的第一个任务
        Runnable firstTask;
        /** Per-thread task counter */
        //Worker 执行完毕的任务个数
        volatile long completedTasks;
          .....
}

核心配置参数

 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;
    }

面试常考:
corePoolSize:在线程池中始终维护的线程个数。
maxPoolSize:在corePoolSize 已满,队列也满的情况下,扩充线程到该值。
keepAliveTime/TimeUnit: maxPoolSize 中的空闲线程,销毁所需要的时间。总线程回收至corePoolSize.
TimeUnit 是时间单位。
blockingQueue:线程池所用的队列类型。一定要设置队列大小。
threadFactory: 线程创建工厂,可以自定义。一般我们采用默认值。
RejectedExecutionHandler:corePoolSize 已满,队列已满,maxPoolSize已满,最后的拒绝策略。

线程池的执行流程如图:

【深入理解 线程池】_第2张图片

首先是判断corePoolSize,其次判断blockingQueue是否已满,接着判断maxPoolSize.最后使用拒绝策略。所以 一定要设置blockingQueue的大小。不然会一直往队列中塞任务,撑爆服务器内存然后宕机。

线程池的优雅关闭

线程池的关闭对比于线程的关闭,更加复杂。 比如 当关闭一个线程池的时候,有的线程正在执行某个任务,有的调用者正在向线程池提交任务,并且队列中还有未执行的任务。因此,关闭过程不可能是立刻马上关闭,需要有一个平滑的过渡,这里就涉及线程池的完整生命周期管理。

线程池的生命周期

线程池中,把线程数量(workCount)和线程池状态(runState)这两个变量打包存储在一个字段里,即ctl变量。如下图,最高位的三位存储线程池状态,其余29位存储线程个数。在jdk6中,这两个变量是分开存储。
【深入理解 线程池】_第3张图片

    //初始化时,线程池状态为RUNNING,线程数为0
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //最高的三位表示线程池的状态
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    //线程池的5钟状态
    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;

    // Packing and unpacking ctl
    //从ctl 中分别解包出runState和workerCount 
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //rs 即runState,wc即workerCount,两个变量打包成ctl一个变量
    private static int ctlOf(int rs, int wc) { return rs | wc; }

从上面的代码可以看出,ctl变量被拆成两半,最高的3位用来表示线程池的状态,低的29位表示线程的个数。线程池的状态有5种。分别是RUNNING,SHUTDOWN,STOP和TERMINATED.
5种状态的迁移过程如图:
【深入理解 线程池】_第4张图片
线程池有两个关闭函数,shutdown()和shutdownNow(),这两个函数会让线程池切换到不同的状态。在队列为空,线程池也为空之后,进入TIDYING状态。最后执行一个钩子函数terminated(),进入TERMINATED状态。线程池才 “结束生命”。
这里的状态迁移,只能从小到大迁移,不能逆向迁移。

正确关闭线程池的步骤

通过上面的分析,我们了解到 线程池的关闭需要一个过程,在调用shutdown 或者shutdownNow之后,线程池并不会立即关闭,接下来需要调用awaitTermination来等待线程池关闭。关闭线程池的正确步骤如下:
调用完 shutdown或者 shutdownNow后。然后循环调用 awaitTermination()方法来判断线程池的状态。

executor.shutdown();
//调用shutdown()后,调用 awaitTermination
try{
	boolean loop=true;
	do{
    loop=!executor.awaitTermination(2,TimeUnit.SECONDS);
    //阻塞,直到线程池里所有的任务结束	
  }while(loop)
}catch(InterruptedException e){
	
}

awaitTermination 函数
不断循环判断线程池是否达到了最终状态TERMINATED,如果达到了,就返回,如果不是,则通过termination条件变量阻塞一段时间。"苏醒"之后继续判断。

public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	//不断循环
            for (;;) {
            	//判断线程池的状态是否是TERMINATED
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                    //如果不是,进行阻塞
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

shutdown 和shutdownNow的区别
1.前者不会清空任务队列,会等待所有任务执行完成,后者会直接清空队列
2.前者只会中断空闲的线程,后者会中断所有的线程。

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
             //判断是否有权限关闭线程池
            checkShutdownAccess();
            //设置状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            //只中断空闲的线程
            interruptIdleWorkers();
            //钩子函数,目前是空的,为自定义的线程池个性化扩展
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
  public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //判断是否有权限关闭线程池
            checkShutdownAccess();
            //设置状态为STOP
            advanceRunState(STOP);
             //中断所有线程
            interruptWorkers();
            //清空队列
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

继续分析下 interruptIdleWorkers()中断空闲线程和 interruptWorkers()中断所有线程

  private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //状态是是非interrutped,并且能拿到锁。work也是继承AQS,说明是空闲的
                //此时才执行interupt,中断信号
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
  void interruptIfStarted() {
            Thread t;
            //只要线程启动,并且状态不是interrupted。就执行中断信号
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

shutdown和shutdownNow 最后都执行了tryTerminate();
tryTerminate不会强行终止线程池,只是做了检测。当 workerCount为0,workerQueue为空时,先把状切换为TIDYING。然后调用钩子函数terminated,随后把状态改成TERMINATED。最后执行 termination.sinaAll().通知前面阻塞在awaitTermination的所有调用线程。

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
			//当workQueue为空,workCount为0时,才会到这
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            		//cas 修改状态为 TIDYING
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                    	//调用钩子函数
                        terminated();
                    } finally {
                    	//设置状态为 TERMINATED
                        ctl.set(ctlOf(TERMINATED, 0));
                        //唤醒所有线程--在awaitTermination
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

任务的提交过程分析

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的值
            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);
    }

addWorker 此函数用于开一个新的线程,如果第二个参数core为true,则用corePoolSize作为上界,如果为false,则用maxPoolSize作为上界。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //如果线程池的状态为SHUTDOWN,说明线程池进入了关闭过程
            // 并且 1. 线程状态已经大于 SHUTDOWN 就直接返回 false,
            //      2.新加的任务 不是null.也不能加任务了,则直接返回 false
            //     3. 队列是空的,也不加任务了,直接返回 false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                	//线程数超过了上界,则不会创建,直接返回false;
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                    //worker加一,则跳出循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                //重新判断c.如果值不相等,说明有其他线程增加了进来。则需要从新循环
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

		//到这里说明worker加1了,后面的逻辑就是具体实例化一个worker.
        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());
					//再次校验rs 的值。即线程池的状态
					//线程池的状态还是 RUNNING,
					//如果是 SHUTDOWN.可以加null任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            //线程还没启动,此时是alive().则抛出异常
                            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)
            //如果加入线程失败。则把workerCount减一
                addWorkerFailed(w);
        }
        return workerStarted;
    }

任务的执行过程

在上面的任务提交过程中,可能会开启一个新的Worker,并把任务本身作为firstTask赋给该Worker。但对于一个Worker来说,不是只执行一个任务,而是源源不断地从队列中取任务执行,这是一个不断循环的过程。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        //对于的线程
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        //对于的任务
        Runnable firstTask;
        /** Per-thread task counter */
        //统计线程完成的任务数量
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            //初始值设置的是-1
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            //调用的是ThreadPoolExecutor 的runWorker方法
            runWorker(this);
        }
      }

runWorker

 //运行的任务有两种类型 一种是线程数不到核心线程数或者队列满了线程数不到最大线程数,直接new Work执行
// 从队列中取
 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        // 从worker中取的任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	//不断循环从队列中获取任务执行
        	//task 首先从work中取,取不到然后再从队列中取(getTask())
            while (task != null || (task = getTask()) != null) {
                //执行任务先加锁。此处对应了shutdown 来判断线程是否是空闲时的操作tryLock.
                //如果能获取到锁,说明没有任务执行,线程是空闲的。
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                //对线程池和线程状态的校验判断,
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    //不符合就interupt,给自己发送中断信号
                    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;
                    //完成任务数加1
                    w.completedTasks++;
                    //释放锁
                    w.unlock();
                }
            }
            //判断是正常退出还是异常退出,用于finally里面里面进行worker退出的逻辑处理
            completedAbruptly = false;
        } finally {
            //worker退出
            processWorkerExit(w, completedAbruptly);
        }
    }

shutdonw() 与任务执行过程综合分析

把任务的执行过程和上面的线程池的关闭过程结合起来进行分析。当调用shutdown()的时候,可能会出现几种情况
场景一:
当调用shutdown时候,所有线程都处于空闲状态。
这意味着任务队列一定是空的。此时,所有线程都会阻塞在getTask()的地方,然后,所有线程都会收到interruptIdleWorkers()发过来的中断信号。getTask()会响应中断,然后返回null.所有Worker都会退出while循环,然后执行processWorkerExit;

场景二:
当调用shutdown时,所有线程都处于忙碌状态
此时队列可能为空,也可能是非空的,interruptIdleWorkers()内部的tryLock调用失败。什么都不会做,直至队列任务为空。interruptIdleWorkers()内部的tryLock调用成功。此时和场景一一样。

场景三:
当调用shutdown时,部分线程忙碌,部分线程空闲。
有部分线程空闲,说明队列是空的。忙碌的线程按照场景一处理,不忙碌的线程按照场景一处理。

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.
            //rs >= SHUTDOWN 说明调用了shutdown。
            // 并且队列为空 或者 rs >= STOP 调用了shutdownNow ,则返回null 
            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 {
            //1.队列为空,就会阻塞在此处的poll或者take。poll 带超时时间。take不带超时时间。
            //2.一旦收到中断信号,就会进入catch代码,设置 timedOut=false;
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                //可以响应 interruptIdleWorkers 发出的中断信号 
                timedOut = false;
            }
        }
    }


shutdonwNow() 与任务执行过程综合分析

shutdownNow()比较粗暴。和shutdown()相比,多了一个清除队列的操作。少了一些判断而已。

总结

整体来讲,线程池的设计还是比较复杂的。本文首先从整体架构来介绍了线程池,然后逐步从线程池的提交执行和关闭等操作进行了源码分析。希望能对大家理解线程池有所帮助。

你可能感兴趣的:(多线程编程,java,线程池源码,AQS)