java8 ThreadPoolExecutor线程池原理详解

创建线程是一个非常耗时的操作,所以一般在系统中都会创建一个线程池,当有需要执行的任务时,直接从线程池中获取一个线程执行。
在java里面,一般使用类ThreadPoolExecutor创建线程池。本文接下来详细介绍ThreadPoolExecutor的原理。

一、ThreadPoolExecutor的例子

下面先看一个ThreadPoolExecutor的例子。

public class Main {
    public static void main(String argv[]){
        ThreadPoolExecutor threadPool=new ThreadPoolExecutor(1,2,
                10, TimeUnit.MINUTES,new ArrayBlockingQueue(5));
        AtomicInteger cnt=new AtomicInteger(0);
        Runnable r=()->{System.out.println("启动线程"+cnt.getAndAdd(1));};
        threadPool.execute(r);
        threadPool.execute(r);
        threadPool.execute(r);
    }
}

上面代码创建一个核心线程数为1,最大线程数为2的线程池,Runnable表示需要执行的任务,使用ThreadPoolExecutor.execute()方法执行任务。如果线程池中有空闲线程会立即执行任务,如果没有,则将任务放到阻塞队列中。
ThreadPoolExecutor提供了非常丰富的构造方法:

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {}

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {}

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {}

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

构造方法的各个入参含义如下:

  • corePoolSize:核心线程数,这是线程池中维持的最小线程数,如果超过了该数字,那么线程空闲一段时间后会被销毁;
  • maximumPoolSize:线程池中所能维持的最大线程数,超过了corePoolSize的线程数属于非核心线程;
  • keepAliveTime和unit:如果线程超过了核心数,那么当线程空闲了keepAliveTime指定的时间后,会被销毁,unit表示keepAliveTime的单位;
  • workQueue:阻塞队列,如果线程池中所有的核心线程都在运行任务,此时还有任务加入,那么任务会被放入阻塞队列中,待阻塞队列也被放满后,会创建新非核心线程执行任务,核心线程数+非核心线程数<=maximumPoolSize;
  • threadFactory:线程工厂,ThreadPoolExecutor内部都是使用threadFactory创建线程,java提供有默认的线程工厂DefaultThreadFactory;
  • handler:如果线程池的线程数已经达到了最大线程数,而且所有的线程都在执行任务,阻塞队列也满了,那么ThreadPoolExecutor是无法再增加新任务,新加入的任务都应该拒绝,ThreadPoolExecutor提供了一个RejectedExecutionHandler用于处理这种场景下的新加任务,java提供了四种拒绝策略,也就是RejectedExecutionHandler的四个实现类。

二、源码分析

任务使用Runnable对象表示,调用ThreadPoolExecutor.execute()便可以执行任务,当然ThreadPoolExecutor中也提供了其他的执行任务的方法,但是execute()是最核心的,理解了该方法的逻辑,其他的方法也就很好理解了,下面将重点介绍execute()方法。

1、准备知识

ThreadPoolExecutor使用了一个AtomicInteger类型的属性ctl来表示线程池状态和线程数量,ctl的定义如下:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ThreadPoolExecutor使用ctl的高3位表示线程池状态,低29位表示线程数量,默认线程池状态是RUNNING,线程数是0。
ThreadPoolExecutor使用如下常量表示线程池的状态:

    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int RUNNING    = -1 << COUNT_BITS;//高3位为111
    private static final int SHUTDOWN   =  0 << COUNT_BITS;//高3位为000
    private static final int STOP       =  1 << COUNT_BITS;//高3位为001
    private static final int TIDYING    =  2 << COUNT_BITS;//高3位为010
    private static final int TERMINATED =  3 << COUNT_BITS;//高3位为011

各个状态的含义如下:

  • RUNNING:运行状态,可以接受新任务并且处理队列中的任务,也是默认状态,该状态也是这些状态中唯一一个负数,因此判断线程池状态是否是RUNNING,只需判断是否小于0即可。
  • SHUTDOWN:关闭状态(调用了shutdown方法)。不接受新任务,,但是要处理队列中的任务。
  • STOP:停止状态(调用了shutdownNow方法)。不接受新任务,也不处理队列中的任务,并且要中断正在处理的任务。
  • TIDYING:所有的任务都已终止了,workerCount为0,线程池进入该状态后会调 terminated()方法进入TERMINATED 状态。
  • TERMINATED:终止状态,terminated() 方法调用结束后的状态,默认terminated() 是一个空实现。

这些状态的转换关系如下图:

图片引自:https://blog.csdn.net/generalfu/article/details/110946298

在这里插入图片描述

介绍完了准备知识,下面正式进入execute()方法。

2、execute()

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();//获取ctl值
        //workerCountOf()方法通过位运算得到线程数
        //下面的if判断是如果当前线程数小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
        	//addWorker()的第二个入参表示是否使用核心线程执行任务,true表示使用
        	//addWorker()里面会创建新线程执行任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果线程数超过了核心线程数,或者addWorker()执行失败,那么会进入下面的分支判断
        //workQueue就是构造方法中的入参阻塞队列,下面将任务放入到阻塞队列中
        if (isRunning(c) && workQueue.offer(command)) {
        	//任务放入阻塞队列成功后,需要再次检查线程池状态,因为在多线程环境下,线程池的状态随时可以发生变化,
        	//如果此时线程池关闭了,那么需要将任务从阻塞队列中删除,
        	//如果此时线程池中没有存活的线程了,那么需要创建一个线程执行阻塞队列中的任务
            int recheck = ctl.get();
            if (!isRunning(recheck) && remove(command))
            	//如果线程池不是运行状态,那么将任务从阻塞队列中删除,
            	//任务交给RejectedExecutionHandler处理
                reject(command);
            else if (workerCountOf(recheck) == 0)
            	//阻塞队列新增任务后,而线程池中的线程数为0,那么需要新建线程执行任务
                addWorker(null, false);
        }
        //如果阻塞队列已满,使用非核心线程执行任务,如果失败,则调用RejectedExecutionHandler处理
        else if (!addWorker(command, false))
            reject(command);
    }

通过execute()方法可以基本知道线程池如何工作的,用下图总结一下execute()的执行流程:
在这里插入图片描述
注意:当线程数不超过核心线程数时,每次新来一个任务都会创建一个线程,这样做的目的是尽快使线程池中的线程数达到coreSize。
execute()里面调用了多个方法,下面一一介绍这些方法的执行逻辑。

3、addWorker()

addWorker()根据入参core的不同,判断是使用核心线程处理任务还是使用非核心线程。只要是线程池状态处于RUNNING,且线程池个数没有超过限定值,那么便会新建线程,处理任务,否则该方法返回false。

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);//rc表示线程池状态
            //如果线程池已经停止,或者已经关闭,那么不会再接收新任务,直接返回false
            //如果线程池已经关闭,新任务也是null,且阻塞队列中没有需要执行的任务,该方法直接返回false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
			//如果线程池的状态检查合法,那么下面开始检查线程数
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                	//根据入参core的设置,判断是使用核心线程还是非核心线程
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;//如果线程数超过了限定值,直接返回false
                if (compareAndIncrementWorkerCount(c))//使用CAS修改线程数
                    break retry;//线程数修改成功,进入下一步
               	//如果线程数修改不成功,则再次循环尝试修改
                c = ctl.get(); 
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
        //到这里为止,说明线程池状态合法,线程数也已经修改成功,那么下面便可以新建线程处理任务了
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        	//Worker里面会新建Thread对象,该类在后面会介绍
        	//一个Worker对象其实也就是一个工作线程,它负责执行任务
            w = new Worker(firstTask);
            final Thread t = w.thread;//t表示新建的Thread对象
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();//加锁,这里加锁主要是为了防止其他线程修改线程池状态和workers
                try {
                    int rs = runStateOf(ctl.get());
					//如果线程池处于RUNNING,或者不是RUNNING但新任务是null,
					//那么将工作线程放入集合中(workers),该集合主要作用是记录当前有多少存活的线程
                    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;
    }
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);//将工作线程从集合中删除
            decrementWorkerCount();//减少线程数
            tryTerminate();//该方法在下面介绍
        } finally {
            mainLock.unlock();
        }
    }

addWorker()的代码很多,但是其逻辑很简单,首先检查线程池状态,然后检查线程数是否超过了限定值,如果两者都没有问题,那么便增加线程数,新建线程并启动线程。如果在启动线程前或者启动过程中出现异常,会调用addWorkerFailed()修改线程数,并检查是否停止线程池。

4、remove()

当线程池处于非运行状态时,那么添加到阻塞队列的任务需要再从阻塞队列删除,删除任务调用remove()。
线程出处于非运行状态,说明已经调用过shutdown()或者shutdownNow()之类的方法,说明需要停止线程池运行。

    public boolean remove(Runnable task) {
        boolean removed = workQueue.remove(task);//从阻塞队列删除任务
        tryTerminate(); //检查线程池是否要停止,中断空闲线程
        return removed;
    }
    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                //如果线程池处于RUNNING状态,或者处于TIDYING状态,
                //或者线程池处于SHUTDOWN但还有任务需要执行,那么该方法不做任何处理,直接返回
                return;
            // 如果线程池处于SHUTDOWN,且没有任务需要执行,或者处于STOP,那么需要中断空闲线程
            if (workerCountOf(c) != 0) { 
            	//这里只中断一个线程,shutdown()方法会中断所有的线程
            	//我理解这里中断一个线程的原因是为了效率的考虑,因为shutdown()已经开始中断所有的线程了
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            	//如果没有待执行任务,修改线程状态为TIDYING,修改线程数为0
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();//空方法
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));//修改线程状态为TERMINATED
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }

5、reject()

该方法非常简单,直接将任务交给RejectedExecutionHandler处理了。

    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

6、Worker类

到这里,execute()方法的基本处理流程介绍完了,但是还有一个Worker类没有介绍,现在我们知道Worker类里面会创建一个Thread对象并执行任务,但是是怎么执行的,阻塞队列里面的任务什么时候执行?还有一系列的问题没有解决,下面我们深入Worker类。
在addWorker()方法里面,会新建一个Worker对象处理任务,一个Wokrer对象持有一个Thread对象,因此Wokrer也就相当于一个工作线程,下面看一下该类源码:

	//代码有删减,Worker继承自AQS,Worker是一个独占锁
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        //持有的线程对象
        final Thread thread;
        //待执行任务
        Runnable firstTask;
        //执行任务计数器
        volatile long completedTasks;

        Worker(Runnable firstTask) {
            setState(-1); //设置AQS的属性state值,线程运行前,会将state修改为0
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);//创建Thread
        }
        public void run() {
            runWorker(this);//运行任务
        }
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }
		//加锁
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
	
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
		//加锁
        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        //解锁,线程运行前,必须先执行该方法,将state设置为0
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }
		//中断线程
        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

Worker继承自AQS,因此Worker也是一个锁对象,而且是一个独占锁(使用锁的原因的是防止Worker执行任务的过程中被中断)。启动线程后,线程会执行Worker的run()方法,而run()方法又去调用ThreadPoolExecutor.runWorker()方法。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;//firstTask是通过execute()方法传入的,表示第一个需要执行的任务
        w.firstTask = null;
        w.unlock(); //要想使Worker的锁可用,必须先调用unlock()方法
        boolean completedAbruptly = true;
        try {	
        	//getTask()可以从阻塞队列里面获取待执行任务
            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++;//任务计数器加1
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	//有两种情况会执行下面的方法,一是执行任务过程中抛出异常,二是任务全部执行完毕退出循环,
        	//前一种情况入参completedAbruptly为true,后一种情况completedAbruptly=false
        	//如果是前一种情况,processWorkerExit()会调用addWorker()方法新建一个线程
        	//如果是后一种情况,根据线程池中的线程个数判断是否新建线程,如果线程池中至少有一个活动线程,那么便不新建,否则调用addWorker()方法新建
            processWorkerExit(w, completedAbruptly);
        }
    }

runWorker()首先执行execute()方法传入的任务,该任务执行完后,调用getTask()方法从阻塞队列里面获取任务继续执行,如果任务全部执行完,或者抛出异常,那么退出执行,进入processWorkerExit()方法。如果是因为异常退出的,那么便新建一个线程继续执行任务,如果是因为任务全部执行完退出的,那么根据线程池中目前的线程个数判断是否新建线程。
总的来看,runWorker()的逻辑还是简单的。
runWorker()里面会调用getTask()方法从阻塞队列里面拿任务,下面再来看一下该方法如何执行的。

    private Runnable getTask() {
        boolean timedOut = false; 
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            //检查线程池是否已经关闭或者停止了
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();//线程数减1
                return null;
            }
            int wc = workerCountOf(c);//wc表示线程个数

            //allowCoreThreadTimeOut默认是false
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			//如果线程数大于最大线程数,或者大于核心线程数且阻塞队列中没有待执行任务,
			//那么ThreadPoolExecutor可能会缩减线程数
            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;//任务不为null,直接返回
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

总结一下getTask()方法的执行流程:

  1. 如果当前线程数大于最大线程数,那么该方法直接返回,停止线程运行;
  2. 如果当前线程数大于核心线程数,且阻塞队列里面没有待执行任务,那么该方法直接返回,停止线程运行;
  3. 如果当前线程数大于核心线程数,但是阻塞队列里面还有待执行任务,那么使用带超时时间的poll()方法从阻塞队列里面获取新任务;
  4. 如果当前线程数小于等于核心线程数,那么直接从阻塞队列里面获取待执行任务,如果阻塞队列为空,那么线程会一直阻塞,直到有任务可以执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值