首页 > 其他分享 >线程池易忘知识点

线程池易忘知识点

时间:2023-04-29 16:56:52浏览次数:27  
标签:知识点 null 队列 池易 任务 task 线程 执行

What

主要用于整理线程中容易忘记的点以及不太好理解的内容

shutdown vs shutdownNow

两者都是用于关闭线程池,但是也有着很大区别

shutdown方法行为

  • 会使得线程池的状态变成SHUTDOWN,线程池不再接收新来的任务。
  • 中断空闲的线程(从阻塞队列拿不到任务被阻塞),正在执行任务的线程不会被中断。
  • 不会移除阻塞队列中等待的任务。
  • 会执行完所有的任务后才会变成最终态TERMINATED(因为不会移除队列中的任务)。

shutdownNow方法行为

  • 会使得线程池的状态变成STOP,线程池不再接收新来的任务。
  • 中断所有运行的线程,无论是空闲还是正在执行任务的线程都会被中断。
  • 移除阻塞队列中等待的任务。
  • 执行完当前正在执行的任务就会变成最终态TERMINATED(因为会移除队列中的任务)。

shutdown方法中断线程时如何区分是空闲的线程还是正在执行的任务的线程?

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        // 中断空闲线程
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
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();
    }
}
/**
 * 此方法线程执行的核心逻辑, 线程启动后, 调用run方法, 而run方法调用该方法
 */
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 {
                    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 {
        processWorkerExit(w, completedAbruptly);
    }
}

线程何时退出

线程退出逻辑主要在runWorkergetTask方法中。

  • 线程池状态是SHUTDOWN以及阻塞队列为空时(shutdown方法含义, 因为还要执行队列中剩余的任务)。
  • 线程池状态是STOP时(shutdownNow方法含义,不需要判断队列是否为空,并且shutdownNow方法会移除队列中剩余的任务)。
  • 从阻塞队列中获取任务时超过指定时间并且线程池中的数量大于核心线程数量时(核心线程并不是通过标记来实现,只是简单通过比较数量)。
  • 线程执行任务发生异常时会直接退出循环,不会再从阻塞队列中获取任务执行。

前面3点均体现在getTask方法中,后面1点体现在runWorker方法中。

/**
 * 线程执行的核心逻辑, 循环获取任务执行
 */
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 {
                    // 对应于第4点, 执行任务时发生异常
                    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 {
        processWorkerExit(w, completedAbruptly);
    }
}
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

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

        // 对应于第1点和第2点
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 对应于第3点
        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;
        }
    }
}

runWorker方法中退出时的finally代码块会执行processWorkerExit方法,用于收尾工作。其中还有两点巧妙的逻辑

  • 会执行tryTerminate方法,终止线程池(shutdownshutdownNow方法也会调用此方法,但是由于可能当时还有正在执行的线程,终止不掉,因此需要线程退出时再调用该方法)。
  • 如果线程执行任务异常结束,会新建一个新的线程补上,这可以避免线程池里的线程执行任务时全部异常结束掉,而没有新线程来执行队列中的剩余任务。

中断线程的目的?

shutdownshutdownNow都会去中断线程,主要有以下两个目的。

  • 唤醒从阻塞队列中获取任务时导致等待的空闲线程,再一次去执行getTask方法中for循环的逻辑,从而退出。
  • 用户代码也很有可能通过线程中断来退出run方法,事实上通过线程中断来退出线程也是比较推荐的一种方法,这样便可以终止正在执行任务的线程,不过这取决于用户代码实现。

invokeAll

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
    long timeout, TimeUnit unit) throws InterruptedException;

此方法用于批量提交任务,并等待任务全部执行完毕或者超时才会返回。

  • 返回来的Future的isDone方法必然是true,future肯定是完成状态,正常结束、异常结束、被取消都有可能。
  • 此方法除了会抛中断异常,还会抛RejectedExecutionException,毕竟内部调用的execute方法。
  • 当超时发生时,会逐个调用每一个Future对象的cancel方法,并中断执行任务的线程。此时已经完成的任务不受任何影响,因为任务已经是完成状态了,cancel方法会直接返回;正在执行的任务状态被置为取消,但是任务该怎么跑还是怎么跑;未执行的任务状态被置为取消,轮到该任务执行时,也会直接结束,因为已经是取消状态了,不会执行后续逻辑。任务为取消状态时执行get方法会抛CancellationException异常,而不是返回null

Executors.newCachedThreadPool()

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

达到的效果是按需创建线程,首先核心线程数量为0,也就超过60s后,还取不到任务,那么线程会退出。

其次SynchronousQueue是一个容量为0的队列,只有put和take成对出现时才不会被阻塞。线程池判断队列是否满了的方法是调用阻塞队列的offer方法,该方法不会阻塞,队列满返回false,否则返回true。对于SynchronousQueue来说,如果没有别的线程从队列中拿元素,则会返回false(容量为0),所以会创建一个新的线程来执行任务。当然了,因为指定线程的空闲存活时间是60s,也就意味着线程执行完任务后会从队列中拿任务,最多等待60s,在这60s之内,往队列里塞新的任务,此时offer会返回true,就不会创建一个新的线程了,由这个空闲的线程来执行。

标签:知识点,null,队列,池易,任务,task,线程,执行
From: https://www.cnblogs.com/wt20/p/17364208.html

相关文章

  • PHP重要知识点
    PHP表单和用户输入PHP中的$_POST和$_GET变量用于检索表单中的信息,比如用户输入。实例:<html><head><metacharset="utf-8"><title>test</title></head><body><formaction="welcome.php"me......
  • 【协程】进程,线程和协程
    进程进程,描述的是程序的执行过程,是运行着程序的代表,在操作系统中,每个进程的内存空间都是独立的,使用多进程并发有两个缺点:一是内核的管理成本高,而是无法简单地通过内存同步数据(进程运行的虚拟内存空间),很不方便,于是多线程模式就出现了。线程线程是操作系统能够运行运算调度的最......
  • CSS知识点总结
    CSS知识点总结文章内容可能较多且杂乱,可以查看页面右方的目录,以及使用Ctrl+F搜索页面内容进行内容定位。常用属性推荐搭配文档使用,可以复制属性名,到文档查看该属性对应的可选值。......
  • 实验2 多线程
    创建一个线程#include<stdio.h>#include<unistd.h>#include<pthread.h>#include<sys/types.h>void*threadFunc(void*arg){printf("InNEWthreaad\n");}intmain(){ pthread_ttid;//createthreadfunctionpthread_create(......
  • 《牛津英语》七年级易错知识点汇总.
    《牛津英语》七年级易错知识点汇总.  单词拼写短语搭配句型语法U6pleasantconvenientswim-swam-swumexcitingoppositefinancialbalconykindergartenpleasant-plea-sure-pleased tellsb.aboutsth.onthemapoftakeabustotake......
  • HashMap为什么存在线程不安全呢?
    关注Java后端技术栈“回复“面试”获取最新资料本文主要探讨下HashMap在多线程环境下容易出现哪些问题,深层次理解其中的HashMap。我们都知道HashMap是线程不安全的,但是HashMap在咱们日常工作中使用频率在所有map中确实属于比较高的。因为它可以满足我们大多数的场景了。上面展示了......
  • STM32:RTthread_线程
    1微处理器系统    随着产品功能的增多,裸机系统不能够满足产品需求,引入RTOS实时操作系统的多线程管理,可以增加程序的稳定性逻辑性,便于管理;2线程  通常默认一个能独立实现功能的函数,称之为线程;多线程管理的意思就是这个程序可以实现多个功能管理;  2.1线程栈   ......
  • 电容知识点小结
    1、钽电容过滤纹波效果非常好,但是钽电容容易爆,质量不好,过压、接反均会导致其爆掉。爆的过程有明火,在爆炸火灾等级要求高的场合尽量不适用钽电容。应注意固体钽电容在高阻电路中瞬时击穿可自愈,使用时回路中应串联电阻器,阻值按照3欧姆每福特为宜。2、铝电解电容,一般最高电压就做......
  • Java多线程之---用 CountDownLatch 说明 AQS 的实现原理
    本文基于jdk1.8。CountDownLatch的使用前面的文章中说到了volatile以及用volatile来实现自旋锁,例如java.util.concurrent.atomic包下的工具类。但是volatile的使用场景毕竟有限,很多的情况下并不是适用,这个时候就需要synchronized或者各种锁实现了。今天就来说一下几......
  • Java 中的几种线程池,你之前用对了吗
    好久不发文章了,难道是因为忙,其实是因为懒。这是一篇关于线程池使用和基本原理的科普水文,如果你经常用到线程池,不知道你的用法标准不标准,是否有隐藏的OOM风险。不经常用线程池的同学,还有对几种线程的使用不甚了解的同学可以读一下此文。为什么要使用线程池虽然大家应该都已经很清......