ThreadPoolExecutor的使用及源码分析

ThreadPoolExecutor自己也经常使用,也看过几次源码,但是源码具体执行流程在经过一段时间之后有些就变得模糊。所以还是在此记录一下ThreadPoolExecutor源码中的关键点和自己对代码的理解。
在文章前面部分介绍一下ThreadPoolExecutor相关知识点、使用流程(该部分内容参考:https://www.jianshu.com/p/ae67972d1156,感谢其作者),在后面部分会着重分析源码执行的关键路径和我对源码执行理解,从源码角度对前面部分的使用做一个对应。

一、ThreadPoolExecutor的基本介绍

1、构造方法中各参数的理解:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);
  • int corePoolSize:该线程池中核心线程数最大值。

核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。

  • int maximumPoolSize: 该线程池中线程总数最大值。
    线程总数 = 核心线程数 + 非核心线程数。

  • long keepAliveTime:该线程池中非核心线程闲置超时时长。
    一个非核心线程,如果不工作(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。

  • TimeUnit unit:keepAliveTime的单位(直接参考TimeUnit 类中的定义)

  • BlockingQueue workQueue:该线程池中的任务队列:维护着等待执行的Runnable对象。
    当所有的核心线程都在工作时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。

  • ThreadFactory threadFactory:创建线程的接口,可以自己实现或者使用默认,我一般自己实现,可以修改默认线程名,使线程名和项目相关。

private static class ThreadFactory implements java.util.concurrent.ThreadFactory {
       private final AtomicInteger mCount = new AtomicInteger(1);
       @Override
       public Thread newThread(@NonNull Runnable r) {
           Thread result = new Thread(r, "my_thread" + "-" + mCount.getAndIncrement());
           result.setDaemon(false);
           return result;
       }
   }
  • RejectedExecutionHandler handler:拒绝策略,抛出异常专用,一般不用自己定义。

2、各种任务队列的使用策略:

  • SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,不会保存在工作队列之中,当线程池中的线程数大于maximumPoolSize的时候就会抛出异常。

  • LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务。因为LinkedBlockingQueue队列长度默认是Integer.MAX_VALUE,所以不会有非核心线程的存在,在线程数量达到核心线程数之后,每次都会直接将任务添加到该队列之中,而不会启动新的线程,所以maximumPoolSize是失效的。

  • ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则抛出错误。

  • DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务。

二、从源码的角度分析ThreadPoolExecutor的使用

该部分着重以ThreadPoolExecutor结合LinkedBlockingQueue的使用分析线程池使用的关键路径,本部分大部分以代码注释的方式分析。
看源码之前抛出问题:①添加任务的过程是怎么样的;②核心线程怎么保持不被销毁的;③核心线程、非核心线程、工作队列之间的协调。

1、ThreadPoolExecutor中的几个状态和几个方法:

	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
    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;
   
	// 计算当前线程池的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //计算当前线程的个数
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //根据状态值和线程数合成ctl
    private static int ctlOf(int rs, int wc) { return rs | wc; }
    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }
  • AtomicInteger类型常量ctl:用来保存当前线程池状态和当前Worker个数,它的初始值和二进制表示为:-536870912(11100000000000000000000000000000)
  • CAPACITY:类似于掩码,用来辅助计算状态值和workerCount的,值:536870911(00011111111111111111111111111111)
  • RUNNING状态:该状态的线程池会处理所有的任务,包括新来的和阻塞队列中的,值:-536870912(11100000000000000000000000000000)
  • SHUTDOWN状态:该状态的线程池不会再接收新任务,但还会处理已经提交到阻塞队列中等待处理的任务,值:0
  • STOP状态:不会再执行任何任务,且中断正在执行的任务,值:536870912(00100000000000000000000000000000)

从上面可以看出,线程池设计上是以一个int类型的值的前3位表示当前线程池的状态,后面的位数标识当前正在执行的任务的个数(Worker)。

2、从execute()方法开始:
从该方法看出线程池新建线程执行任务的策略:
①当前线程数小于核心线程数的时候直接新建线程执行任务;
②如果队列能够添加任务,则优先将任务添加到队列,而不是新建进程;
③在队列已满的时候,如果线程数小于最大线程数,则新建非核心线程执行任务,否则报错。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        int c = ctl.get();
        //当前正在工作的线程小于核心的线程数,跳转到addWorker()方法,新建线程执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //大于等于核心线程数:当前线程在运行状态,且添加到任务队列成功了。
        //对于LinkedBlockingQueue队列,添加任务总会成功(它的任务队列长度很大),所以代码走这里,
        //对于SynchronousQueue因为添加失败(不能添加任务到队列),所以不会从这个分支走。
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //重新检查状态,可能这个时候线程池被停止了,要remove新添加的任务
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //判断当前线程数是不是0,如果是,则新建一个线程执行新加到队列里面的任务,否则会没有线程去执行新添加进阻塞队列的任务(command)。
            //如果当前线程数大于0,则不需要添加,因为有线程在运行,在线程执行完成之后,后唤醒阻塞队列里面的任务,新加到阻塞队列里的任务还是能够被执行。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //对于SynchronousQueue会走到这里,所以,在线程数小于最大线程数的时候,会新建线程执行任务,当大于最大线程数时,会直接报错reject(),此时addWorker()会失败
        else if (!addWorker(command, false))
            reject(command);
    }

3、addWorker()源码分析:

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

            //在SHUTDOWN状态下,如果队列不为空,且firstTask == null,阻塞队列里面还有未完成的任务,则还是会继续执行后面的操作,
            //说明了在该状态下,能够执行阻塞队列里面的任务,firstTask为空说明了是添加了一个执行阻塞队列里面任务的线程
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                //获取当前正在执行的线程数,判断添加进来的任务是不是核心任务,如果是,则正在执行线程数要<=corePoolSize ,
                //如果不是,则正在执行线程数要<=maximumPoolSize。否则新建线程失败。
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //线程数增加1,添加成功,跳出循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
	        //新建一个Worker,下面说明这是个什么东西
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());
					//当前线程池的正在运行或者是SHUTDOWN状态且firstTask为空,则要新建线程去执行任务。
                    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) {
	                //会执行Worker的run()方法
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

4、Worker类是什么?继承自AbstractQueuedSynchronizer实现了Runnable接口(AQS相关可以参考文章源码分析AQS独占锁、共享锁和Condition的实现),构造方法中会初始化thread变量,AbstractQueuedSynchronizer提供了线程锁的相关功能,所以Worker具有获取和释放锁的能力。

 private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        final Thread thread;
        Runnable firstTask;

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            //使用factory新建线程
            this.thread = getThreadFactory().newThread(this);
        }
		//执行线程时,会调用该方法,runWorker()的时候会执行firstTask,可以看出Worker是对Runable的一层封装,通过Worker的run()方法,执行实际的Runable的run()方法。
        public void run() {
            runWorker(this);
        }
    }

5、runWorker()方法,该方法开始在线程中执行我们真正的任务:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //将Worker中的Runnable拿出,并置空
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
	        //如果Worker里面任务不为空或者队列不为空,则执行任务,
	        //getTask()会去取阻塞队列里面的任务,根据阻塞队列的策略,可能会让核心线程进入等待状态。在下面会分析
            while (task != null || (task = getTask()) != null) {
                w.lock();
                //这里判断线程池状态,确保STOP、TIDYING和TERMINATED状态下,如果线程没有被中断,设置线程被中断标志,
                //但并不一定会立即退出执行,因为如果线程中没有sleep()、wait()、Condition、定时锁等操作, interrupt()方法是无法中断当前的线程的,
                //如果在interrupted状态下,执行了上述的相关操作,会抛出InterruptedException异常,直接走到finally的流程,
                //这样会再次确保不会有核心线程在STOP状态下还阻塞的情况,因为核心线程的存在依赖于wait()方法。
                //STOP状态是在shutdownNow()方法中设置的,下面分析shutdownNow()和shutdown()方法的区别
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
	                //提供给开发者的一个hook,可以在任务开始之前处理一些事情
                    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 {
	                    //提供给开发者的一个hook,可以在任务开始处理完之后处理一些事情
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    //记录完成的任务数
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
	        //无论什么情况最终都会将线程从workers中移除
            processWorkerExit(w, completedAbruptly);
        }
    }

6、分析shutdown()shutdownNow()方法,为什么shutdownNow()之后任务并不一定会停止?下面贴出主要的源码,两个方法都会遍历调用interrupt()方法,在该方法调用之后,如果有核心线程被阻塞,都会在上面runWorker()方法中抛出异常,被清除(processWorkerExit()方法)。

public void shutdown() {
	//设置SHUTDOWN状态
	advanceRunState(SHUTDOWN);
	interruptIdleWorkers();
}
private void interruptIdleWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //w.tryLock(),尝试获取Worker里面的锁,也是和shutdownNow()方法主要区别之一,
                //在上面的runWorker()方法中也获取了该锁,所以如果该worker正在执行,这里会被阻塞,t.interrupt()方法要在该worker执行完成之后才会调用
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
            }
        } finally {
            mainLock.unlock();
        }
    }
 
 public List<Runnable> shutdownNow() {
		//设置STOP状态
        advanceRunState(STOP);
        interruptWorkers();
        return drainQueue();
    }
 private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
	        //这里不会获取worker的锁,也就是不管该worker是不是正在运行,都强行设置中断
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

7、核心线程是怎么长期驻留的(被阻塞):getTask(),该方法负责从阻塞队列里取出一个任务。

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 这种状态下,清空线程数
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);
			//allowCoreThreadTimeOut为true,代表允许清除核心线程,线程池中不允许存在核心线程
			//如果线程数大于了核心线程数,则该变量也为true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			//在线程数大于最大线程数的时候要清除该线程,线程数减1,返回null,将线程清除
			//timed为true,且超时还没有拿到任务且队列为空的情况下,也会清除线程
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
	            //这里会根据timed执行阻塞队列的不同方法,在后面会分析LinkedBlockingQueue的这两个方法,
	            //线程数在大于核心线程数的时候会执行poll(),否则执行take()方法,LinkedBlockingQueue的take()方法在队列为空时会阻塞线程,
	            //keepAliveTime就是设置的线程存活时长,等待keepAliveTime的时间之后,如果还没有新的任务进入队列,则设置超时标志timedOut
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                //超时之后还没有新的任务可以执行,则设置timedOut标记                    
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

8、分析LinkedBlockingQueueoffer(E e)poll(long timeout, TimeUnit unit)take()方法:
①offer方法:

public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        //队列已满直接return false,添加失败。
        if (count.get() == capacity)
            return false;
        int c = -1;
        Node<E> node = new Node<E>(e);
        //获得生产者的锁
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            if (count.get() < capacity) {
	            //队列不满,入队,队列长度加1
                enqueue(node);
                //先取出之前任务数再加1
                c = count.getAndIncrement();
                //如果在放入该任务后,队列还没有满,则唤醒一个等待在notFull上的线程,
                //因为signal()函数只会唤醒一个阻塞线程,因此可能有其它线程的put操作也被阻塞着。
                if (c + 1 < capacity)
                    notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        //如果队列任务数为0,由于刚刚加入一个任务,刚刚好队列状态由empty->not empty,则唤醒一个等待在notEmpty的线程,以便可以继续取出任务执行
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

②poll方法:

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E x = null;
        int c = -1;
        long nanos = unit.toNanos(timeout);
        final AtomicInteger count = this.count;
        //获取消费者的锁
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
	        //当前队列里如果为空了,则等待一段时间
            while (count.get() == 0) {
	            //等待时间结束之后,还没有新的任务加入,则返回null
                if (nanos <= 0L)
                    return null;
                //notEmpty是一个条件队列,在取任务时使用,用来执行等待超时、解除等待操作。
                nanos = notEmpty.awaitNanos(nanos);
            }
            //这个时候代表有数据了,执行出队操作
            x = dequeue();
            //获取当前队列中的任务数,并将任务数减1
            c = count.getAndDecrement();
            //如果在该任务出队后,队列还没有空,则唤醒一个等待在notEmpty上的线程,
            //因为signal()函数只会唤醒一个阻塞线程,因此可能有其它线程的poll操作也被阻塞着。
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        //如果队列任务数等于队列容量,由于这里取出了一个任务,刚刚好队列状态由full->not full状态,如果有线程等待,则唤醒一个等待在notFull上的线程。
        if (c == capacity)
            signalNotFull();
        return x;
    }

③take方法:

public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
	        //如果队列长度为0,直接阻塞,相比于poll()没有等待超时返回null这个操作,
	        //这也是getTask()为什么能够让核心线程在没有任务执行的情况下阻塞而不是被清除的原因。
            while (count.get() == 0) {
                notEmpty.await();
            }
            //如果本来队列就不为空或者新添加进来了任务,则出队
            x = dequeue();
            c = count.getAndDecrement();
            //同poll()操作
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        //和poll()方法中的解释一样
        if (c == capacity)
            signalNotFull();
        return x;
    }

总结:以上分析了ThreadPoolExecutor配合BlockingQueue的使用,知其然知其所以然才能在使用中更加得心应手。在具体实际应用中,如有必要,也可以通过定义自己的阻塞队列来实现不同的出队入队逻辑,通过源码可以了解其设计、增加一些新知识,让自己成长的更快。
还是那句话,当你想了解一些源码设计、对一些框架一知半解、使用时总会出一些bug的时候:reading the fucking source code。

猜你喜欢

转载自blog.csdn.net/weixin_38062353/article/details/82691498