Java并发——线程池ThreadPoolExecutor类源码解析

版权声明:个人博客:blog.suyeq.com 欢迎访问 https://blog.csdn.net/hackersuye/article/details/85042239

    线程池作为减少线程创建关闭开销的常用手段,一直被使用着,ThreadPoolExecutor类则是Java中内置的线程池,大部分使用Java作为第一语言的人写并发程序都会用到这个类。以源码的层次聊聊线程池的原理与实现。

ThreadPoolExecutor与其它类的关系

    ThreadPoolExecutor类继承自AbstractExecutorService抽象类,AbstractExecutorService又实现了ExecutorService接口,ExecutorService接口继承了Executor接口,其中Executor接口的源码如下:

public interface Executor {
    void execute(Runnable command);
}

    只有一个行为,该行为是用来提交到线程池中的任务的,而ExecutorService接口则扩充了Executor接口:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
 //......
}

    从其扩展的行为来看,ExecutorService 接口在提交任务的基础上增加了对线程池的管理操作,而且还提供了一个新的提交方法submit方法,该方法返回一个Future对象,一般来说它返回的就是FutureTask类对象,用来获得任务执行的结果,如果对FutureTask类不熟的同学可以参照这篇博客 Java并发——FutureTask源码解析,而且在AbstractExecutorService实现的sunbmit的源码里,是调用了execute方法来实现的,所以后面只分析execute方法。

    对于创建一个线程池,一般不采用之间new的方式,而是采用Executes类的静态方法来创建:

public class Executors {
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    //.....
}

    今天所讨论的线程池只是最基本的线程池,即固定线程数量的大小,而Executors 类里面还有很多种类的线程池的创建,这些线程池在以后讲解。

ThreadPoolExecutor类的状态

    在源码中,定义了一系列的状态变量来表示线程池当前处于的状态,它的状态如下:

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;
  1. RUNNING状态:表示线程池初始的状态以及正在执行任务的状态,该状态下可以接受新的任务,可以处理阻塞队列中的任务;
  2. SHUTDOWN状态:调用了shutdown方法后的状态,该状态下,线程池不能接受新的任务,但是可以处理阻塞队列中的任务;
  3. STOP状态:调用了shutdownNow方法后的状态,该状态下,线程池不能接受新的任务,也不能处理等待队列中的任务,而且会中断正在执行的任务;
  4. TIDYING状态:是一个过渡状态,该状态下所有任务都已终止,运行的线程为0,且将要调用terminated方法;
  5. TERMINATED状态,terminated方法结束后的状态;

    它们的转变有这几种:

1.当调用shutdown方法,或者隐式的调用了finalize方法时:
RUNNING -> SHUTDOWN

2.当调用了shutdownNow方法
(RUNNING or SHUTDOWN) -> STOP

3.当阻塞队列和工作线程都为空:
SHUTDOWN -> TIDYING

4.当线程池为空:
STOP -> TIDYING

5.当terminated完成
TIDYING -> TERMINATED

    在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;
//计算线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//计算工作线程的数量
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

    从源码中可以看出,利用位运算,32位的int类型前3位保存线程池的状态,后29位保存线程池中工作线程的数量。

ThreadPoolExecutor类的成员变量

    下面介绍 ThreadPoolExecutor类的重要的成员变量:

扫描二维码关注公众号,回复: 5761253 查看本文章
	private final BlockingQueue<Runnable> workQueue;
    private final ReentrantLock mainLock = new ReentrantLock();
    private final HashSet<Worker> workers = new HashSet<Worker>();
    private final Condition termination = mainLock.newCondition();
    private int largestPoolSize;
    private long completedTaskCount;
    private volatile ThreadFactory threadFactory;
    private volatile RejectedExecutionHandler handler;
    private volatile long keepAliveTime;
    private volatile boolean allowCoreThreadTimeOut;
    private volatile int corePoolSize;
    private volatile int maximumPoolSize;
  1. workQueue:一个阻塞队列,其实用来缓存的,当线程池中的线程都在执行任务时,新来的任务都会进入到这个阻塞队列中,等待线程有空再从这个阻塞队列中获取到此任务再执行
  2. mainLock :全局锁,用来实现线程的同步
  3. workers :保存work对象,work对象接下来详细讲解
  4. termination :Condition,用于线程间的协调通信
  5. corePoolSize:线程池中核心线程数,这是个很重要的变量,一般代表着线程池中最低的能保持线程效率最高的线程数
  6. maximumPoolSize:表示线程池中能运行的最多的线程数,它与corePoolSize的区别在于,假设一个工厂里面有4台机器,对应着4个工人,这4个工人可以高效的利用这四台机器,但是新来的工人也可以使用机器,但是效率可能还比不上只有4个工人的时候,因为机器换人操控需要时间,在其中corePoolSize就是4,而maximumPoolSize就是里面的总工人数
  7. largestPoolSize:线程池中的线程数量
  8. keepAliveTime:线程池中线程池中空闲线程存活的时长
  9. allowCoreThreadTimeOut:一个布尔值,表示是否允许核心线程有空闲时间的限制,如果值为true,那么当线程不管超过核心线程数还是未超过,线程池中的线程都有空闲时间限制,如果为false,只有当线程数超过核心线程数的时候线程才有空闲时间限制,当未超过时,则没有空闲时间限制
  10. threadFactory:线程的创建工厂
  11. handler:当阻塞队列中任务已满以及线程数达到最大,那么拒绝接受任务,并执行拒绝的策略

ThreadPoolExecutor类的工作者线程

    ThreadPoolExecutor类里面对Thread类进行了封装,封装成Worker类,该类继承自AQS框架,且实现了Runnable接口,它的源码如下:

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        private static final long serialVersionUID = 6138294804551838833L;
        //该Worker的封装的运行线程,即工作者线程,
        //在线程池中运行的线程则为工作者线程
        final Thread thread;
        //该工作者线程执行的第一个任务
        Runnable firstTask;
        //表示该工作者线程完成的任务
        volatile long completedTasks;
        Worker(Runnable firstTask) {
        	//先给state状态值设为-1,表示不能由任何线程获得Worker锁
            setState(-1); 
            this.firstTask = firstTask;
            //从线程工厂创建一个新的线程
            this.thread = getThreadFactory().newThread(this);
        }
        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); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }
		//......
        }
    }

    不得不说,AQS并发框架在Java的并发中用的真的多,如果有同学对AQS并发框架不熟的,可以参照这篇文章Java并发——AQS框架源码详解。从源码中可以看出,Worker的内部既有工作者线程,同时它本身是一个独占锁,该锁用来在其工作者线程内上锁的,一旦工作者线程内的使用资源被上了锁,我们就可以来判断该工作者线程是忙碌的,而不是空闲的。

ThreadPoolExecutor类的任务执行

    上文说到,ThreadPoolExecutor类的任务执行与提交,最终调用的是excute方法,该方法源码如下:

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 = ctl.get();
        }
        //执行到这步,表明创建工作者线程失败或者线程数大于核心线程数
        //如果线程池的状态为RUNNING,且任务插入成功
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //这里再次判断一次,防止线程池状态改变被中断
            if (! isRunning(recheck) && remove(command))
            	//执行拒绝策略
                reject(command);
             //如果工作者的线程数为0了,那么创建一个工作者线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //执行到这一步,表明阻塞队列已满需要创建一个新的线程
        //或者线程池的状态大于等于SHUTDOWN状态
        else if (!addWorker(command, false))
        	//增加失败,执行拒绝策略
            reject(command);
    }

    总结一下上述流程,首先判断工作者线程数量是否小于核心线程数,当小于时,则表明可以创建一个新的工作者线程,并将任务赋给该工作者线程作为第一个任务,因为这时候创建工作者线程会提高并发的效率。如果创建工作者线程失败或者工作者线程数目不小于核心线程数,那么表明该任务需要提交到阻塞队列中去。当任务进入阻塞队列成功,则再次判断线程池的状态是否改变,如果改变了,那么则执行拒绝策略,并移除阻塞队列。当在阻塞队列已满的情况下,这时候工作者线程都在执行任务,那么则需要创建一个新的工作者线程来执行该任务,创建失败则执行拒绝策略。新建一个工作者线程addWorker方法源码如下:

//firstTask为创建工作者线程的第一个任务
//core值表明是否以核心线程数作为线程池中最大线程数
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            //计算线程池的状态
            int rs = runStateOf(c);
            //当满足线程池的状态大于等于SHUTDOWN状态时,且满足以下情况之一
            //1.线程池状态大于SHUTDOWN状态
            //2.firstTask不为null
            //3.workQueue为null
            //则返回创建工作者线程失败
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                 //CAS原子操作对工作者线程数加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 {
            w = new Worker(firstTask);
            //从Worker对象中取出新的线程
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                //全局加锁,保证同步
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());
                    //如果线程池的状态小于SHUTDOWN 状态
                    //或者线程池的状态是SHUTDOWN状态且第一任务为null
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //加入workers集合中
                        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;
    }

    从新增工作者线程的源码中可以看出,它的创建并不是当线程池中工作者线程的数量小于核心线程数就创建,大于或等于则复用之前的线程,而是当线程池中工作者线程的数量小于核心线程数则创建新的工作者线程,当前者的数量大于等于后者的时候,如果阻塞队列中已满的情况下,也会创建一个新的工作者线程,其它情况复用已创建的工作者线程。
    当启用工作者线程时,从Worker的源码中看到,它调用了runWorker方法,该方法的源码如下:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        //释放work锁,刚开始是都加锁的
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	//当first不为null或者从阻塞队列中获取任务不为null
            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方法是钩子方法,用于让子类实现
                    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也是钩子方法
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    //work处理的任务加1
                    w.completedTasks++;
                    //释放锁,表明该工作线程空闲
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	//被中断后,工作线程退出
            processWorkerExit(w, completedAbruptly);
        }
    }

    工作线程不断利用循环从阻塞队列中取得任务执行,当被异常中断后,调用processWorkerExit方法处理异常的线程,而且在processWorkerExit方法内部如果条件允许可以再次创建一个线程来代替被异常中断并移除works集合的线程。beforeExecute方法与afterExecute方法是让子类实现的,可以在这两个方法里记录数据以及完成收尾工作。从阻塞队列中取出任务的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.
            //如果阻塞队列为null且线程池的状态大于等于SHUTDOWN 状态
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                //工作者线程减1
                decrementWorkerCount();
                return null;
            }
            int wc = workerCountOf(c);
            //判定工作者线程空闲时间
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            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;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

    其中用了keepAliveTime与allowCoreThreadTimeOut 来决定如何从队列中取出任务并返回,如果没有设置等待的空闲时间,那么便一直等待,设置了等待时间,如果没在规定的时间内返回则返回一个任务为null。

线程池ThreadPoolExecutor的实现类

    ThreadPoolExecutor线程池在Executors里的实现有以下几种:

//创建固定核心线程数量以及最大线程数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO)执行
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

ThreadPoolExecutor类的拒绝策略

    当线程池中的工作者线程达到最大数量时,那么线程池会拒绝新来的任务,它的拒绝策略主要有以下四种:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。//默认的处理方法
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

ThreadPoolExecutor线程池的关闭

    调用shutdown方法关闭线程池,源码如下:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); // 关闭的时候需要加锁,防止并发
    try {
        checkShutdownAccess(); // 检查权限
        advanceRunState(SHUTDOWN); // 线程池状态更设置为SHUTDOWN
        interruptIdleWorkers(); // 中断闲置的工作者线程
        onShutdown(); // 钩子方法
    } finally {
        mainLock.unlock(); // 解锁
    }
    tryTerminate(); // 尝试结束线程池
}

    该方法是温和的关闭线程池,不会接受新的任务,但是会让阻塞队列中的任务执行完毕,回收空闲的工作者线程。另外一种关闭方法则是shutdownNow方法,源码如下:

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); //加锁,防止并发
    try {
        checkShutdownAccess(); // 检查权限
        advanceRunState(STOP); // 线程池状态设置为STOP
        interruptWorkers(); // 中断工作者线程的运行
        tasks = drainQueue();
    } finally {
        mainLock.unlock(); // 解锁
    }
    tryTerminate(); // 尝试结束线程池
    return tasks;
}

    该方法结束线程池比较粗暴,会拒绝接受新的任务,也会回收所有的工作者线程,桶时不会执行阻塞队列中的任务。

线程池核心线程数的设置

    借鉴网上的说法是:如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1,如果是IO密集型任务,参考值可以设置为2*NCPU,NCPU表示着你CPU是几核的,可以通过RunTime类来获取自己电脑的CPU的核数:

CPU的核数=Runtime.getRuntime().availableProcessors();

猜你喜欢

转载自blog.csdn.net/hackersuye/article/details/85042239