ThreadPoolExecutor线程池相关源码分析

目录

前言

一、线程池状态

二、Worker类

三、线程池运行过程

1.execute

2.addWorker

3.runWorker

4.getTask

5.processWorkerExit

四、关闭线程池

总结


前言

       线程池在java编程中的应用十分广泛,本文从源码上分析线程池运行原理,从任务的提交,运行直到任务结束,线程池销毁。


一、线程池状态

        线程池有running,shutdown,stop,tidying和terminated五种状态,刚开始创建的时候是running状态,调用shutdown()函数变为shutdown状态,调用shutdownNow()函数变为stop状态,上面两个函数都是调用tryTerminate()函数尝试停止线程池,当线程池中没有线程存在时,能够将状态更改为tidying和terminated,终止线程池。

     *   RUNNING:  Accept new tasks and process queued tasks 线程池的初始化状态
     *   SHUTDOWN: Don't accept new tasks, but process queued tasks 不接收新任务,但能处理已添加的任务。
     *   STOP:     Don't accept new tasks, don't process queued tasks,
     *             and interrupt in-progress tasks 不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
     *   TIDYING:  All tasks have terminated, workerCount is zero,
     *             the thread transitioning to state TIDYING 当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。
     *             will run the terminated() hook method
     *   TERMINATED: terminated() has completed 线程池彻底终止,就变成TERMINATED状态。

二、Worker类

       worker类维持线程运行过程中的中断控制,继承了AQS,在任务开始执行的时候获取锁,完成后释放锁。worker的状态有-1,0,1三种,在worker刚创建的时候状态值是-1,执行任务的时候状态值是1,等待获取队列中任务的时候状态值是0,这种状态是空闲状态。

        //尝试获取锁,使用CAS,当前状态值为0的时候能够获取到锁,设置为1
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //释放锁,将worker状态设置为0
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

三、线程池运行过程

1.execute

       在程序中使用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 = ctl.get();
        } //尝试将任务添加到任务队列中
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get(); //再次确认,如果当前线程池状态不是Running状态,拒绝任务,从队列中删除任务,在remove中会调用tryTerminate,避免因为队列新添加的任务导致
            if (! isRunning(recheck) && remove(command))//线程池无法终止。
                reject(command);//如果当前线程数量为0,新建线程,当
            else if (workerCountOf(recheck) == 0)  //添加一个没有任务的线程,运行时直接从队列中获取任务,这种新建的线程在队列中获取任务时允许被中断
                addWorker(null, false);//
        }
        //添加非核心线程
        else if (!addWorker(command, false))
            reject(command);
    }

2.addWorker

       线程池中的线程是以worker形式存在的,addworker方法负责向线程中添加线程。firstTask非空时,说明该线程是为了执行新添加的任务创建。 当前状态为shutdown时,不会接收新任务,创建线程是为了处理队列中的任务,所以如果firstTask非空或者任务队列为空,会添加失败;当前状态是stop时,不再接收新任务,也不会处理队列中的任务,所以不能创建线程。

       在线程池中的线程本就没有核心线程和非核心线程之分,参数core只不过是为了维持线程的特定数量,如果想要创建核心线程,发现当前线程数已经达到了核心线程数,会返回失败,然后会再尝试将当前任务添加到任务队列中,从而在任务量不大的情况下,能够维持线程数等于核心线程数。

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            //当线程池状态大于shutdown,不再接收和处理任务,返回false;当线程池状态是shutdown时,
            // Check if queue empty only if necessary. 处理任务队列中的任务,不接受新的任务,所以firstTask!=null或者任务队列为空时返回false
            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;
                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 {//创建的是非守护线程,有非守护线程程序就不会停止,所以只要核心线程数大于0,并且不设置allowCoreThreadTimeOut,线程池执行任务之后程序就不会自动停止
            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());
                    //再次判断线程池的状态,当线程池状态小于shutdown,或者线程池状态为shutdown,并且firstTask==null(线程创建用来执行队列中的任务)
                    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;
    }

3.runWorker

       在worker添加之后,会调用runWorker方法,当firstTask不为空时,首先执行firstTask,然后循环执行队列中的任务(getTask),如果firstTask为空,直接执行队列中的任务。此处使用阻塞队列,能够保证worker在获取不到任务的时候进入阻塞状态,释放cpu资源。

       在任务执行的开始,首先使用lock()方法尝试获取,执行完成后使用unlock()方法释放锁。在shutdown方法中,首先使用tryLock()方法判断当前线程是否是空闲线程,当线程执行任务的过程中,会占有锁,tryLock会返回false,shutdown方法无法中断运行任务中的线程。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts 将worker的状态从-1变为0,在运行任务之前允许中断,为了应对没有对firstTask赋值的worker,在队列中获取任务的时候可以中断
        boolean completedAbruptly = true; //为啥不直接把状态设置为0,可中断状态
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock(); //当前线程尝试占有锁,占有锁之后变为非空闲线程,只有stop的时候会中断
                // 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())
                    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++;
                    w.unlock(); //任务执行完成,释放锁
                }
            } //任务执行过程中没有异常会将completedAbruptly置为false
            completedAbruptly = false;
        } finally { //处理线程退出的情况,getTask返回null或者任务执行发生异常
            processWorkerExit(w, completedAbruptly);
        }
    }

4.getTask

       getTask负责从队列中获取任务,当获取不到任务并且返回null值时,会导致当前线程退出,线程数减一。当线程池状态是stop时,不再处理任务,直接返回null;当线程池状态是shutdown时,只处理任务队列中的任务,不再处理新任务,如果任务队列为空,直接返回null。

        线程也会因为超时而退出,当线程池允许当前线程超时退出并且已经超时,当前线程返回null,退出。如果线程池允许核心线程超时或者当前线程数大于核心线程数,就允许线程超时,此时会使用poll方法从队列中获取任务,超时获取不到做标记;如果当前线程数小于核心线程数并且不允许核心线程超时,使用take方法从队列中获取任务,获取不到会无限期等待,直到其他线程打断(shutdown)。

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

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            //当前状态是stop状态或者shutdown状态并且任务队列为空时,返回null,当前状态shutdown,并且任务队列不空,继续向下执行,这里如果同时有多个线程判断任务队列不为空,
            // Check if queue empty only if necessary.  都会从任务队列中取任务,当设置核心线程不过期的时候,核心线程会一直等待,但其中必然有一个线程能够拿到任务运行完成,
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {   //执行完任务之后再次调用getTask方法,由于任务队列此时为空,会返回null,
                decrementWorkerCount();                                    //调用processWorkerExit方法,其中会调用tryTerminate()方法,尝试中断一个空闲线程,产生传播效应
                return null;                                               //所以调用调用shutdown方法会在执行完任务队列中的任务之后关闭,调用stop方法会在执行完已经开始执行的
            }                                                              //任务后关闭,不会执行队列中的任务

            int wc = workerCountOf(c);
            //判断当前线程能不能因为超时终止,这里是不区分核心线程和非核心线程的,只是保持线程的并发度,如果线程数大于核心线程数或者允许核心线程超时,当前函数就可以返回null,
            // Are workers subject to culling?外面跳出循环,终止当前线程。。
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))  //如果线程数大于最大线程数或者已经超时,并且线程数量大于1或者任务队列为空,返回null
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            } //在线程的中断
            //所谓的空闲线程就是在等待获取任务的线程,此时可以抛出InterruptedException异常,
            try {//抛出之后重新查看有没有可运行任务,在线程池调用shutdown()之后线程池状态改变,如果队列中没有任务,return null,在外面终止线程
                Runnable r = timed ?   //如果允许核心线程超时或者当前线程数大于核心线程数,使用poll方法获取任务,允许超时退出。
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :  //
                    workQueue.take();   //当线程数不大于核心线程数,并且不允许核心线程超时,使用take方法,取不到任务会一直阻塞,程序也不会停止。
                if (r != null)         //主程序中新建线程池,并且执行了少量任务,部分线程会一直卡在这里,程序不会关闭
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

5.processWorkerExit

        processWorkerExit方法会在线程退出之前执行,当线程执行任务的过程中出现异常或者getTask方法返回null值时,当前线程会退出。在该方法中会调用tryTerminate方法,设计到shutdown方法之后的传播逻辑。如果线程因为异常而退出,或者当前线程数小于核心线程数,会重新创建一个线程添加进去执行任务。

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();  //如果因为异常退出,需要将worker数减一(正常退出情况下已经在getTask里面做了)

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);  //将w从线程池中移除
        } finally {
            mainLock.unlock();
        }
        //当队列中的任务为空时,会在此处terminate线程池,在shutdown状态下,能进入下面说明任务队列不为空
        tryTerminate(); //当线程池状态是shutdown时,如果还有多个线程,会尝试中断其中一个,然后又会执行这个方法,便是传播效应。
        //即使没有调用shutdown方法,当线程池中的非守护线程全部停止时,就不会阻止程序停止。
        int c = ctl.get();//如果当前线程池位于stop之后的状态,不需要保持线程池中线程数至少唯一,在这些状态下不接受新任务,也不会处理已添加的任务,没必要维持线程
        if (runStateLessThan(c, STOP)) {//shutdown函数中会尝试中断所有空闲线程,当队列中还有任务时,也会维持最小线程数。shutdownNow会尝试中断所有线程,也不会维持最小线程数。
            if (!completedAbruptly) {  //在不允许核心线程超时的情况下,最小线程数是核心线程数,否则最小线程数为0,当队列不为空时,最小线程数至少为1
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }//线程异常退出时,直接添加另外一个线程,添加的意义何在。在当前是shutdown状态并且任务队列为空时,添加线程会失败,从而保证线程数减少
            addWorker(null, false);  //在队列为空时,不会出现tryTerminate中断一个线程,线程退出后有添加一个线程的情况。
        }
    }

四、关闭线程池

       线程池提供了两个关闭线程池的方法,shutdown方法将线程池状态更新为shutdown,中断不执行任务的线程,在getTask方法中,如果当前线程在等待从队列中获取任务,被中断会报InterruptedException异常,然后判断当前状态,状态已经被更新为shutdown,如果队列为空,getTask方法会返回null,线程退出。该方法会将队列中的任务执行完成之后将线程池关闭。。

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);  //将线程池状态置为shutdown
            interruptIdleWorkers();  //中断空闲线程,使用tryLock,如果能够获取到锁,证明该线程没有执行任务,执行任务的过程中会占有锁,
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

       shutdownNow方法将线程池状态更新为stop,中断state>=0的线程,线程退出逻辑基本一致,该方法执行之后会清空队列中的任务并返回。。

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);   //将线程池状态更新为stop
            interruptWorkers();      //将运行中的任务中断(设置中断标志)
            tasks = drainQueue();   //清空任务队列,shutdown方法不会清空,会将队列中的任务执行完
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;      //返回队列中的任务
    }

       两个方法都会调用tryTerminate方法,在该方法中,如果当前线程数为0,会将线程池状态更新为tidying以及terminated;当有线程运行时,会尝试中断其中的一个线程,当线程退出时,会再次调用tryTerminate方法,从而产生传播效应,将所有线程中断。

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||  //如果当前线程池状态在运行状态或者是Tidying或者terminated,直接返回;如果当前线程池状态是shutdown并且任务队列为空,直接返回。
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE); //进入到这里,线程池状态是shutdown或者stop,如果还有线程,尝试中断其中一个,
                return;  //线程如果成功退出,会再次调用这个方法,从而产生传播效应,同时会尝试addworker,但不会添加成功。
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {  //将线程池状态置为tidying,此时线程池中线程数量为0
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();
                    } finally {  //将线程池状态置为tidying
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

总结

       本文对java线程池源码进行分析,主要是线程的执行和退出过程,已经线程池的关闭,如有不足之处,欢迎大家指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值