深入Java线程池的执行和关闭流程

1.线程池状态

​​线程池状态

​​2.线程池状态流转图

状态流转图

3.线程池工作流程图

工作流程图

4.主要参数


	//任务队列
 	private final BlockingQueue<Runnable> workQueue;

    //线程池锁
    private final ReentrantLock mainLock = new ReentrantLock();

    //工作线程队列 使用HashSet存储
    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;

    //线程存活时间,即线程等待任务阻塞最大时间(getTask()方法中应用)
    private volatile long keepAliveTime;

    //允许核心线程过期,默认false,如果true,则共用keepAliveTime
    //使用allowCoreThreadTimeOut(boolean value)可打开
    private volatile boolean allowCoreThreadTimeOut;

    //核心线程数
    private volatile int corePoolSize;

    //允许的最大线程数
    private volatile int maximumPoolSize;

    //默认拒绝策略
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

5.主要方法介绍

5.1.execute方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
       	/*
       	1.如果运行的线程小于corePoolSize,则尝试使用给定的命令作为第一个
       	任务来启动新线程。对addWorker的调用会自动地检查runState和
       	workerCount,从而通过返回false来防止在不应该添加线程的情况下添加
       	从而导致错误。
       	2.如果一个任务可以成功地排队,那么我们仍然需要再次检查是否应该添加
       	一个线程(因为现有的线程在最后一次检查后死亡),或者池在进入这个方法
       	后关闭。所以我们重新检查状态,如果需要,如果停止,则回滚队列;如果没
       	有,则启动新线程。
       	3.如果无法对任务排队,则尝试添加新线程。如果它失败了,我们知道我们
       	被关闭或饱和,因此拒绝任务。
       	*/
        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();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

5.2.Work对象

其继承了AQS,利用state状态来判断线程是否空闲(线程执行任务时会先通过lock来获取锁)

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        //调用这个工人worker的线程
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** 将任务监听循环委托给外部运行工人,核心方法  */
        public void run() {
        	//
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        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(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

5.3.runWorker(this)

下面介绍runWorker(this)这个核心方法

/** 
  主流程循环。重复从队列获取任务并执行它们,同时处理一些问题:
  1.我们可能从一个初始任务开始,在这种情况下,我们不需要得到第一个。否则,只
  要池在运行,我们就从getTask获取任务。如果返回null,则工作进程将由于更改池状态
  或配置参数而退出。外部代码中的异常抛出导致跳出循环,在这种情况下
  completed参数保持为true,这通常会导致processWorkerExit替换这个线程(所以要尽
  量在Runnable业务代码中try catch异常并处理,否则会导致线程池销毁并重新创建线
  程)
  2.在运行任何任务之前,锁需要被获取,以防止任务执行过程中中断,然后我们确保除
  非线程池停止,否则这个线程不会有任何中断。
  3.每个任务运行之前都有一个对beforeExecute的调用,这可能会抛出一个异常,在这
  种情况下,会导致线程死亡(completed true 循环中断),任务也不会执行。
  4.假设beforeExecute正常完成,我们运行这个任务,收集它抛出的任何异常并发送给
  afterExecute。我们分别处理RuntimeException、错误(规范中保证我们会捕捉到这两个
  错误)和任意的可抛出对象。因为我们不能在Runnable.run中重新抛出Throwables,所
  以我们将它们封装在错误中(到线程的错误中) UncaughtExceptionHandler)。抛出的异
  常都会导致线程死亡。
  5.run完成后,我们调用afterExecute,它也可能抛出一个异常,这也会导致线程死亡。
  根据JLS 14.20,这个异常即使在task.run抛出时也有效。
  异常机制的最终效果是,afterExecute和线程的UncaughtExceptionHandler拥有关于用
  户代码遇到的任何问题的尽可能准确的信息。
*/
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        /**
        这个参数决定了线程是否会被销毁并重建,如果正常结束则会为false(按照正常流
        程执行,判断线程数是否大于核心线程,小于则创建线程,否则就结束),如果因
        异常而结束则会保持为true,这时会将改线程所属的worker 从workerQueue中
        remove,然后重新添加一个新的worker到队列里,然后该线程执行结束,销毁,也
        就是会重新更换worker
        */
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // 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;
        } finally {
            //结束后处理当前worker,由调度当前worker的线程执行
            processWorkerExit(w, completedAbruptly);
        }
    }

5.4.getTask()

先看下其中一个很重要的方法 getTask() ,循环结束的判断条件是怎样的呢?

/** 
   **执行阻塞或超时等待任务**,取决于当前的配置,或者当这个线程因以下的原因必须退出时返回null:
   1.有多个maximumPoolSize工作者(由于调用setMaximumPoolSize)
   2.线程池被停掉了
   3.线程池关闭,任务队列为空
   4.当worker等待一个任务超时,超时的worker在超时等待前后都有可能被终止,
    (即当 allowCoreThreadTimeOut || workerCount > corePoolSize)
    如果队列不是空的,那么这个worker不是池中的最后一个线程。
*/
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.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            	//keepAliveTime生效的地方,如果是需要超时回收的线程则执行超时阻塞,否则就执行阻塞,直到获取到任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
            	//阻塞时如发现中断,会抛InterruptedException异常,catch处理中断
                timedOut = false;
            }
        }
    }

5.5.processWorkerExit(w, completedAbruptly)

继续看 processWorkerExit(w, completedAbruptly)这个方法做了些什么?

该方法主要对worker进行清理,从hashSet中移除当前的worker,并尝试终止线程池,若线程池未终止,则去判断线程是否需要更换
只有在业务逻辑异常导致线程未正常结束,或者任务正常完成但线程数<允许的最小线程数时,才会新增worker。
(核心线程数在不允许过期时,线程池运行状态下是不会进入这个方法的(要么执行任务,要么会阻塞在getTask()方法里),除非在遇到中断(线程池关闭或其它中断场景)或者业务异常时,才会进入该方法进行核心线程资源的回收和处理)

addWorker(null, false)方法在下面的条件下 不会再新增worker
在这里插入图片描述

/** 
此方法从worker队列中移除线程。
worker清理。仅从worker线程(runWorker方法)调用。除非completed参数被
修改为false,否则假定workerCount已经被调整则考虑退出。
如果线程因用户任务异常而退出,或者运行的工作线程小于corePoolSize,
或者队列非空但没有工作线程,则可能终止线程池或替换worker及线程。
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
		/**
		尝试终止线程池
		将线程池状态转换到终止状态,如果以下条件满足:
		(线程池当前状态为SHUTDOWN&&池和任务队列为空)或(状态为STOP&&池为空)。
		如果满足条件,但worker数量>0,则中断一个空闲工人,以确保关闭信号传播。
		必须在任何可能导致线程池终止的操作之后调用此方法,这样做的主要目的是:
			1.减少空闲worker数量
			2.在关闭期间从队列中删除任务。
			3.线程池的终结
		*/
        tryTerminate();

        int c = ctl.get();
        //如果线程池还没有终止(状态<STOP),则执行下面的逻辑
        if (runStateLessThan(c, STOP)) {
        	//如果worker是正常的任务执行完成,则去执行判断线程数逻辑,根据当前线程数和允许的min值比较,>=min则表示线程无需重建
			//最大线程数类型的线程会在等待存活时间超时后,workers.remove(w);线程执行完毕,回收work,不再新增线程。
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            //当线程数不足,或当前worker是因为异常而跳出任务监听的循环,则重建一个新线程(替换销毁的线程)
            addWorker(null, false);
        }
    }

5.6.tryTerminate()

tryTerminate()又干了什么呢?
一个关键的步骤在 interruptIdleWorkers(ONLY_ONE);

这个方法下面会介绍到,先说下在这里使用该方法有什么作用?
中断一个空闲线程(注意是一个),以使得中断信号可以传播

那么问题来了,为什么只中断一个线程,这种中断信号的传播又是怎么做到的呢?
它的传播机制:
该中断会在getTask()方法阻塞的地方得到响应,这时线程会跳出runTask()循环,进入processWorkerExit方法,继续调用tryTerminate()中断另外一个空闲线程(达到传播的效果,这种机制保证了线程池在shuntdown()时的正常关闭)

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
            	//关键处理步骤,中断一个空闲线程,以使得中断信号可以传播
            	//该中断会在getTask()方法阻塞的地方响应得到响应,线程在runtask()方法里跳出循环后,会进入processWorkerExit方法,
            	//继续调用tryTerminate()传播中断(保证线程池的正常关闭)
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        //唤醒调用awaitTermination阻塞方法的线程,线程池终止完成
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

说到 tryTerminate(); 那就要了解线程池的关闭,看下线程池是如何关闭的,主要涉及以下几个方法
shutdown()
shutdownNow()
awaitTermination(long timeout, TimeUnit unit)
finalize()

5.7.shutdown()

首先来看shutdown()方法做了什么

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
       		//检查关闭进入许可
            checkShutdownAccess();
            //将线程池状态改为SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中断空闲工人线程
            interruptIdleWorkers();
            // hook for ScheduledThreadPoolExecutor
            onShutdown(); 
        } finally {
            mainLock.unlock();
        }
        //尝试线程终止结束
        tryTerminate();
    }

5.8. interruptIdleWorkers()

其中主要关闭方法为 interruptIdleWorkers();
负责中断空闲线程,通过tryLock判断线程是否空闲(线程运行或更改会加锁)

 private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

5.9.shutdownNow()

下面来看shutdownNow()
可以看到区别在于 interruptWorkers();这个方法,改方法中断了所有线程,包括在执行任务的线程,且最终将未完成队列返回

public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //线程池状态改为STOP
            advanceRunState(STOP);
            //中断所有线程
            interruptWorkers();
            //取任务队列未完成任务
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        //返回未完成任务队列
        return tasks;
    }

shutdown() shutdownNow() 都是采用中断方式结束关闭线程池,执行关闭方法后,线程池在添加任务时,若池状态为SHUTODOWN或STOP会直接拒绝任务,抛RejectException异常

5.10.awaitTermination(long timeout, TimeUnit unit)

那**awaitTermination(long timeout, TimeUnit unit)**是做什么呢
该方法若线程池状态为TERMINATED,则返回true,否则会等待timeout时长,若等待超时,则返回false,表示线程仍未关闭终结

 public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

5.11.finalize()

如果我们不手动去shutdown(),那谁来保证线程池的关闭调用呢
答案是finalize()方法,该方法在gc垃圾回收时调用(相当于c++的析构函数),具体为在gc第一次标记完成后会进行一次刷选,筛选的条件是此对象是否有必要执行 finalize()方法,如果该对象被判定为有必要执行finalize()方法,会将该对象统一放到F-Queue里,由一个低优先级的线程去挨个调用其finalize()方法,然后进行第二次小范围标记,然后开始执行清除算法,回收资源(当然对象资源也可以在选择在这个方法里去重新被关联到引用链上,从而避免被回收)。详细介绍看这里:jvm浅析

protected void finalize() {
        SecurityManager sm = System.getSecurityManager();
        if (sm == null || acc == null) {
        	//由jvm在垃圾回收前调用shutdown尝试关闭
            shutdown();
        } else {
            PrivilegedAction<Void> pa = () -> { shutdown(); return null; };
            AccessController.doPrivileged(pa, acc);
        }
    }

5.12.线程池的优雅关闭

那线程池如何优雅的关闭呢,下面介绍一种方式供参考

		//设置关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                executor.shutdown();
                //设定最大重试次数
                int retry = 2;
                try {
                    //每次等待 10 s,重试
                    if (!executor.awaitTermination(10, TimeUnit.SECONDS) && retry-- > 0) {
                        if (retry == 1){
                            //调用shutdownNow()取消正在执行的任务,SHUTDOWN->STOP是被允许的
                            executor.shutdownNow();
                        }else {
                            log.error("线程池任务未正常执行结束");
                        }
                    }
                } catch (InterruptedException ie) {
                    //重新调用 shutdownNow
                    executor.shutdownNow();
                }
            }
        });

jvm的钩子调用过程如下图
在这里插入图片描述
参考:JVM关闭与关闭钩子

ok,先到这里了
当然还有线程池的一些创建方法,因为比较通用,就不做介绍了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值