线程池2(TheadPoolExecutor回收线程)

线程池回收线程三种情况

文章目录

  • 线程池回收线程三种情况
    • 1.线程run方法运行结束
    • 2.shutdown() 关闭线程池
    • 3.shutdownNow()关闭线程池

1.线程run方法运行结束

1.1 工作线程启动进入 work中的run方法中,在run方法中又调用了 runWorker(this)
线程池2(TheadPoolExecutor回收线程)_第1张图片
分析:方法中是一个while 循环,firstTask 是第一个任何,getTask()是从队列中取出任务,while跳出循环且方法执行完之后线程消亡。那么while方法怎么能跳出循环呢,答案就是 getTask() 方法 返回null。

1.2 啥也不说先上源码
线程池2(TheadPoolExecutor回收线程)_第2张图片
条件1
第一种情况:线程池状态是STOP,TIDYING,TERMINATED 直接返回null。

第二种情况:线程池状态是SHUTDOWN并且workQueue队列是空 直接返回null。

注释:返回null 之前 运行 decrementWorkerCount(),工作线程数减1,该方法也没有上锁,会不会出现并发执行,造成工作线程不安全操作?当然不会,该方法中是一个do while 循环 利用CAS方式减1,所以一定回减1.

条件2(该条件难理解)
第一种:情况是工作线程锁大于最大线程锁直接返回null。

第二种:情况从队列中取任务超时(timedOut修改为true),且工作线程数大于核心线程锁(考虑默认情况 allowCoreThreadTimeOut = false,为ture暂不考虑)和工作线程队列为空 返回null。

**结论:**在线程池运行状态,线程池回收线程,条件1 永不成立,条件2 也只有当前工作线程大于核心线程且工作队列为空的时候,该线程会被回收

问: 假如运行线程抛出异常,导致while方法循环方法结束,该线程会回收吗?

答案: 是会回收,但是回收后,线程池会判断当前线程池是否是运行状态或者是shutdwon状态(这里考虑到shutdown状态 必须要把任务队列中的任务消费完),如果是就会重新添加一个线程

上源码证明:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        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 {
                   	//假如该任务抛出异常,会导致while循环结束                    
					 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 {
        	//completedAbruptly = true  说明运行到该地方是异常导致的 
        	//所以该方法内部判断线程池状态如果是runing或者shutdown 会重新添加一个线程
            processWorkerExit(w, completedAbruptly);
        }
    }



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

        tryTerminate();

        int c = ctl.get();
        //该地方判断是否是runing 或者是 shutdown状态
        if (runStateLessThan(c, STOP)) {
        	//抛异常 completedAbruptly = true
            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);
        }
    }


2.shutdown() 关闭线程池

 public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        //获得独占锁,操作成员变量就需要加锁,或者使用CAS修改保证并发安全
        mainLock.lock();
        try {
            checkShutdownAccess();
            //修改线程状态为shutdown
            advanceRunState(SHUTDOWN);
            //interrupt 打断空闲线程,把线程中断标识置为true
            //注释这里是打断空闲线程,是没有获得work对象锁的线程
            interruptIdleWorkers();
            //用于调度线程池执行器的钩子 
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

该方法先获得 对象锁manLock,修改对象成员变量需要保证线程安全,
然后修改线程池状态为Shutdown 该方法为死循环也就是自旋所以一定回成功,在运行该方法之前已经加了对象锁manLock,修改状态成功之后,执行interruptIdleWorkers方法 中断work线程集合中的空闲线程。

  private void interruptIdleWorkers(boolean onlyOne) {
  		//按道理这个地方已经不需要在加锁,之所以加锁是其他地方用到该方法。
        final ReentrantLock mainLock = this.mainLock;
        //该线程已经获得manLock锁,这里为重入获得锁成功。
        mainLock.lock();
        try {
        	//遍历线程集合HashSet
            for (Worker w : workers) {
                Thread t = w.thread;
                //若该线程还未中断且获得锁成功(这里只有空闲线程才会获得成功),那么何为空闲线程,
                //第一种情况线程执行getTask()取任务的时候(执行任务中或线程阻塞在阻塞队列中),
                //第二中情况任务取完,还没有获得w.lock()锁的时候为空闲队列。
                //这里和shutdownNow()关闭的区别就是:shutdownNow()关闭不管是运行中的线程还是空闲线程统统都打算中断标记,也就是没有 w.tryLock(),直接判断!t.isInterrupted()
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

该方法运行结束后,就是所有空闲线程都打上了中断标记。那么执行 t.interrupt() 后是怎么回收线程的呢。

我们姑且把线程分为两种情况:运行中线程和空闲线程(线程被阻塞在阻塞队列中);

空闲线程:空闲线程被阻塞在阻塞队列中,shutdown()方法中执行完成(也有可能执行中断了部分线程),那么被阻塞的线程就会被唤醒
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
被park的线程 ,如果执行了Interrupt 就会被唤醒 park本身是不会抛中断异常, 但是这两个方法实现本身会抛出中断异常。然后执行下一个循环,当执行到条件1的时候,如果阻塞队列为空就返回null,该线程被回收。

假如这个时候任务队列不为空跳过了条件1就会继续执行,取任务。
这个时候已经被中断的线程还能在执行取任务吗,在poll 和take中都是用 lock.lockInterruptibly()获得锁的,难道不会报错吗?答案是肯定不会报错,如下图唤醒会执行 checkInterruptWhileWaiting方法,该方法 用了Thread.interrupted() 返回线程中断标志 这里为ture 已经被中断了,该方法还会把当前线程中断标志重置为false。这就是道格.李的高明之处。
线程池2(TheadPoolExecutor回收线程)_第3张图片

那么继续执行任务的线程已经跳过了条件1,在跳过了条件2,在上次循环被中断的唤醒的线程 会报异常执行 timedOut = false; wc > maximumPoolSize 这个时候也不会出现这种情况,所以会跳过条件2,所以会继续执行, 因为这个时候shutDown()方法已经执行完毕,并且该线程中断标志已经被重置为false?难道这个线程会一直被阻塞在这里吗(线程池中不会在有新的任务进来)。这个问题标记为问题T留在最后回答。

运行的线程:运行的线程也就是获得了w.lock锁,在执行任务没有被打上中断标志的线程,任务运行结束后继续while循环取任务队列中的值也是执行getTask()方法,假如队列不为空跳过条件1,这个时候也会跳过条件2取任务。如此循环直到队列为空 返回null线程回收,或者刚好跳过条件1和条件2 后 队列变空 被阻塞在阻塞队列中(和问题T情况一样)

线程只有问题T中的线程还阻塞在阻塞队列中,他们是怎么被回收的呢。难道是这些线程一直被阻塞吗?答案肯定不是。

上源码:
线程池2(TheadPoolExecutor回收线程)_第4张图片

该方法是while运行跳出后,被执行用来移除work中的线程,并且随机中断一个未中断的线程(每个被回收的线程都会执行)。

 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();
        }
		
		//该方法就是随机中断一个未被中断的线程。
        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

processWorkerExit 该返回中会随机中断一个未被中断的线程,问题T中被阻塞的线程,在他们之前肯定会有一个线程被回收,然后中断问题T中某一线程,重新执行for循环,在条件1的时候返回null被回收,然后在随机中断下一个,直到都被回收。

3.shutdownNow()关闭线程池

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

shutdownNow()和shutdown()区别点:
第一:shutdownNow()不管三七二十一统统给线程打上中断标志而shutdown()只给未获得对象锁(也就是没有执行的运行内容的线程)打上中断标志。

源码证明:

private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
    
	void interruptIfStarted() {
            Thread t;
            //这里只要是未被中断的线程 都会被中断
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

第二:shutdownNow() 会把未被消费的任务从队列中取出放到集合中返回,而shutdown()会运行完已经添加到队列的任务。

线程池2(TheadPoolExecutor回收线程)_第5张图片

 private List<Runnable> drainQueue() {
        BlockingQueue<Runnable> q = workQueue;
        ArrayList<Runnable> taskList = new ArrayList<Runnable>();
        q.drainTo(taskList);
        if (!q.isEmpty()) {
            for (Runnable r : q.toArray(new Runnable[0])) {
                if (q.remove(r))
                    taskList.add(r);
            }
        }
        return taskList;
    }

shutdownNow() 运行后,
1.运行线程:运行的线程继续运行,当前任务运行结束后在去取任务在条件1返回null 线程被回收。

注释:这种线程存在一个风险,运行线程中若出现 sleep,wait(),wait(Long time),join() 则会抛出异常,这种情况线程直接运行结束而回收。
从侧面反应了另外一种情况,只要任务中有异常抛出,若不捕获异常,就会导致该线程被回收。

2.空闲线程:空闲线程在下次取任务的时候,直接在条件1方法返回null

参考:https://www.cnblogs.com/kingsleylam/p/11241625.html

你可能感兴趣的:(java,多线程,并发编程)