源码解读--线程池TheadPoolExecutor深入讲解

21 篇文章 0 订阅

     在使用线程的,我们常使用线程池,线程的复用可以减少线程的开销,很好的提高cpu资源利用率。这篇文章信息量会很大,做好准备哦~ java源码系列

    有对线程池没有直观感觉的同学可以看看这篇文章。在jdk8中,从ThreadPoolExecutor从发,来逐步讲解它的源码实现。

1.线程池定义初始值

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;  //容量限制在2^29-1

//线程池中线程有五种状态
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;  //所有的任务都已经停止了,工作线程的数目为0,线程在转换到改状态是会调用中断方法
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; }  //当前工作的线程
private static int ctlOf(int rs, int wc) { return rs | wc; }     //获取ctl
private static boolean runStateAtLeast(int c, int s) {  //比较最后的状态值
    return c >= s;
}
//判断线程是否正在执行
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

初始化了一定基本变量及方法,下面会经常使用到。

2.执行方法

public void execute(Runnable command) {  //提交线程的执行方法
    if (command == null)  //如果放入线程池中的线程为null,会包空指针异常
        throw new NullPointerException();
    int c = ctl.get();  //获取clt的当前值,这个值会经常被修改
    if (workerCountOf(c) < corePoolSize) { //工作线程个数小于核心线程个数
        if (addWorker(command, true))  //新建一个线程来执行任务
            return;
        c = ctl.get();  //添加线程失败,重新回去ctl的值,下面isRunning方法需要判断线程是否是运行状态
    }
    if (isRunning(c) && workQueue.offer(command)) {  //如果线程的个数大于核心线程池的个数,并且当前线程是运行状态,它就会被放到缓存队列中
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))  //如果放到队列成功了,但是线程池被关了,需要移除掉该线程,没问题的话就拒绝这个线程的执行
            reject(command);
        else if (workerCountOf(recheck) == 0)  //如果工作线程的数量变成0了,添加空的任务,之后其实知道从队列中取任务时会跳过null的
            addWorker(null, false);
    }
    else if (!addWorker(command, false))  //队列满了,如果小于最大线程池的大小,可以新建线程执行任务,如果大于最大线程池大小,就会拒绝任务
        reject(command);
}

总结,总有有四步:

第一步,判断线程是不是为空,如果是空的话,就抛出空指针异常。

第二步,如果线程的数量小于核心线程池的个数,创建新的线程执行任务。

第三步,如果线程的数量大于核心线程的数量,就将线程放到缓存队列中。

第四步,缓存队列的满了,还没有到达最大核心线程池的大小,这个时候就也是创建线程执行任务,如果大于最大的核心线程池的大小,那么就拒绝任务的执行。这里所说执行任务的线程就是Worker.

2.1添加Woker线程

先来看下Worker的定义

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;

    final Thread thread;
    Runnable firstTask;
    volatile long completedTasks;
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }
.......
}
继承了aqs,同时实现了Runable,说明是一个线程.

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:  //retry:  不明白是什么的可以看这篇文章
    for (;;) {
        int c = ctl.get();  //获取到最新的ctl
        int rs = runStateOf(c);  //查看当前线程的状态

        //如果线程的状态是关闭,第一个任务时空,队列是空,直接返回添加任务失败
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||  //需要小于容量
                wc >= (core ? corePoolSize : maximumPoolSize))  //core为true,工作线程要小于核心线程数,core为false,工作线程数要小于最大线程数目。不然,也放回添加任务失败
                return false;
            if (compareAndIncrementWorkerCount(c))  //如果任务个数添加成功,就跳出循环,这个用到了CAS,不明白的可以看这篇文章
                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);  //初始化一个任务
        final Thread t = w.thread;
        if (t != null) {  //当前线程不为空的情况下
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();  //获取锁
            try {
                int rs = runStateOf(ctl.get());  //需要重新获取到线程的运行状态,在获取锁之前,他的状态可能被别的线程改变。
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {  //在线程池是运行状态,或者线程池被关闭了并且第一个线程是空的情况下
                    if (t.isAlive()) // 如果线程是活跃状态,那么说明先状态有问题,直接抛出异常
                        throw new IllegalThreadStateException();
                    workers.add(w);  //将任务加到工作线程集里面
                    int s = workers.size();
                    if (s > largestPoolSize)  //largestPoolSize是记录线程池中出现的最大的线程的数量
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();  //解锁
            }
            if (workerAdded) {
                t.start();  //线程启动,相应的调用run方法
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)  //如果工作线程启动失败,就将它从工作线程集中移除
            addWorkerFailed(w);  
    }
    return workerStarted;
}

总结一下,先校验线程的状态,没有问题就创建一个工作线程,如果线程创建没有问题就启动了,如果有问题就将这个工作线程从工作线程集中移除。

3.运行线程

前面已经讲过了将线程建立起来,现在需要使线程跑起来了。大家都知道要使线程跑起来,肯定要执行start方法。没错,线程池里面也是这样干的。

public void run() {
    runWorker(this);
}

继续往下看,woker里面的run方法调用的是线程池里面的runWorker方法。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();  //取到当前线程
    Runnable task = w.firstTask;  //拿到第一个工作线程的第一个线程
    w.firstTask = null;
    w.unlock(); // 允许出现多个中断
    boolean completedAbruptly = true;
    try {
        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;  //将任务置成null
                w.completedTasks++;  //完成的任务加一
                w.unlock();
            }
        }
        completedAbruptly = false;  //是否完全结束,false表示 完全结束了
    } finally {
        processWorkerExit(w, completedAbruptly);  //清除任务
    }
}

总结:不断的取任务执行,如果执行完成了记录任务的执行数加一,将这个线程从工作任务(工作线程)集中删除。

进一步查看是怎么获取任务的getTask()

private Runnable getTask() {
    boolean timedOut = false; 

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

        // 如果线程池关了,返回的任务时null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 是否允许核心线程数超时,当满足allowCoreThreadTimeOut被设置为true 或当前线程数大于核心线程数时,timed=true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))  //超过最大线程数或超时或任务队列为空
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))  //如果线程数减到了-1 ,就返回null
                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;
        }
    }
}

到此差不多已经基本上讲完了线程池的运行流程,还有一个添加进工作队列的方法没有讲,涉及到了LinkedBlockQueue,已经专门写了一篇文章来讲解。

其实讲了这么多,大家只要知道线程池的使用和运行流程就可以了。它的使用demo在此就不在讲解了,网上也有很多例子。再来温习一下它的运行流程。

第一步,判断线程是不是为空,如果是空的话,就抛出空指针异常。

第二步,如果线程的数量小于核心线程池的个数,创建新的线程执行任务。

第三步,如果线程的数量大于核心线程的数量,就将线程放到缓存队列中。

第四步,缓存队列的满了,还没有到达最大核心线程池的大小,这个时候就也是创建线程执行任务,如果大于最大的核心线程池的大小,那么就拒绝任务的执行。这里所说执行任务的线程就是Worker.

当然如果设置了超时,在时间范围内,任务肯定是可以被执行的,超时返回的就是一个空的任务,不会执行之前的任务。有一个地方需要注意的是:只有在线程数大于核心线程数的时候,任务才会被放到任务队列里面,小于的话,任务时直接执行的。


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值