一站式线程池问题解答

84 阅读9分钟

一、什么是线程池?

     线程池是一种勇于管理和复用线程的机制。它包含一个线程队列和一组线城市管理器,用于创建、销毁和管理线程。线程池可以提供一种线程的复用方式,比变了频繁创建和销毁线程的开销,提高了系统的性能和资源利用率。

   通俗来讲 线程池就是提前创建好一批线程,存储到线城池中,使用的时候获取一个直接来使用。

二、为什么使用线程池

  1. 降低线程创建和销毁的开销:线程的创建和销毁是比较耗费资源的操作,使用线程池可以避免频繁创建和销毁线程,提高系统的性能和资源利用率。

  2. 提高响应速度:线程池中的线程可以立即执行任务,而不需要等待线程的创建和销毁过程,从而提高了任务的响应速度。

  3. 控制线程数量:线程池可以根据配置的参数来控制线程的数量,避免线程过多导致系统资源消耗过大和系统性能下降。

  4. 提供线程管理和监控:线程池提供了线程的管理和监控功能,可以方便地管理线程的状态、执行情况和异常处理等。

总之,线程池是一种管理和复用线程的机制,通过线程池可以提高系统的性能和资源利用率,同时也可以方便地管理和监控线程的状态和执行情况。

     上面提到线程池创建和销毁比较消耗资源,下面一起来了解下,为什么消耗资源,哪些处理过程消耗资源。只有了解了这些我们才能真正体会到线程池带来的快捷。

  1. 上下文切换:线程的创建和销毁需要进行上下文切换,即从一个线程切换到另一个线程的执行环境。上下文切换需要保存和恢复线程的执行状态,包括寄存器、栈、程序计数器等,这些操作都需要消耗一定的时间和资源。

  2. 内存分配:线程的创建需要为线程分配内存空间,包括线程栈、线程私有数据等。而线程的销毁需要释放这些内存空间,这涉及到内存的分配和释放操作,也会消耗一定的资源。

  3. 系统调用:线程的创建和销毁通常需要通过系统调用来完成,例如在操作系统中调用相关的系统函数来创建和销毁线程。系统调用涉及到用户态和内核态之间的切换,这也会带来一定的开销。

  4. 线程同步:线程的创建和销毁可能涉及到线程同步的操作,例如在多线程环境下,需要保证线程的安全性和正确性,可能需要进行锁的获取和释放等操作,这也会消耗一定的资源。

综上所述,线程的创建和销毁涉及到上下文切换、内存分配、系统调用和线程同步等操作,这些操作都需要消耗一定的时间和资源,因此线程的创建和销毁比较消耗资源。为了避免频繁创建和销毁线程的开销,可以使用线程池来管理和复用线程,从而提高系统的性能和资源利用率。

java 中线程池的继承关系

线程池的使用:

1,创建线程

Executors 的创建线程池的方法 创建出来的线程池都实现了ExecutorService接口。常用的方法有以下几种

newFixedThreadPool(int Threads) :创建固定数目线程的线程池

newCacheThreadPool 创建一个可缓存的线程池,调用execute将重用以前构造的线程,如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已经有60秒钟未被使用的线程。

newSingleThreadExecutor 创建一个单线程化的Executor

newScheduledThreadPll(int corePoolSize)创建一个支持定时及周期性的任务执行的线程池。多数情况下可以用来替代Timer 类。

目前项目中 以上这些用法很少使用了, 公司中一般都有动态线程池可以使用。 构造动态线程池 在线程池管理上可以动态改变线程池参数。

就算不用动态线程池,一般也使用  new threadPoolExecutor  自定义参数创建。

线程池的实现原理

execute::

当提交一个新的任务到线程池时,线程池的处理流程如下。 

    • 第一步:如果当前运行的线程数小于核心线程数(corePoolSize),则尝试使用给定的任务作为新线程的第一个任务来启动一个新线程。通过调用addWorker方法,使用原子操作检查runState和workerCount,以防止在不应该添加线程时添加线程。如果成功添加了新线程,则直接返回。

    • 第二步:如果任务可以成功排队,则需要再次检查是否应该添加线程(因为自上次检查以来,现有线程可能已经终止),或者线程池是否已关闭。因此,需要重新检查状态,并根据需要回滚排队操作(如果线程池已停止),或者如果没有线程,则启动一个新线程。

    • 第三步:如果无法将任务排队,则尝试添加一个新线程。如果添加失败,则说明线程池已关闭或已饱和,因此拒绝该任务。

  1. 最后,如果无法执行任务(无法添加新线程或无法将任务排队),则调用reject方法来处理该任务。

这段代码的作用是将任务提交给线程池执行。根据线程池的状态和任务队列的情况,决定是启动新线程执行任务,还是将任务排队,或者拒绝任务。

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();        if (! isRunning(recheck) && remove(command))            reject(command);        else if (workerCountOf(recheck) == 0)            addWorker(null, false);    }    else if (!addWorker(command, false))        reject(command);}

添加worker线程

从方法execute的实现可以看出:addworker 主要负责创建新的线程并执行任务,代码如下

private boolean addWorker(Runnable firstTask, boolean core) {    retry:    for (;;) {        int c = ctl.get();        int rs = runStateOf(c);        // Check if queue empty only if necessary.        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 {        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());                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;}

### 代码解释 这段代码是一个私有方法 `addWorker`,用于向线程池中添加新的工作线程。下面对代码进行逐步解释: 

 1. 首先,通过一个无限循环 `for (;;) { ... }` 进行尝试添加工作线程的操作。 

 2. 在循环中,首先获取当前线程池的控制状态 `c` 和运行状态 `rs`。 

 3. 如果线程池的运行状态大于等于 `SHUTDOWN`,并且不满足以下条件之一,则返回 `false`: - 运行状态为 `SHUTDOWN`,且 `firstTask` 为 `null`,且工作队列不为空。 - 运行状态大于 `SHUTDOWN`。 

 4. 接下来,通过一个内部的无限循环 `for (;;) { ... }` 进行判断是否可以添加新的工作线程。 

 5. 在内部循环中,首先获取当前的工作线程数 `wc`。 

 6. 如果工作线程数大于等于线程池的容量 `CAPACITY`,或者大于等于核心线程数 `corePoolSize`(如果 `core` 为 `true`)或最大线程数 `maximumPoolSize`(如果 `core` 为 `false`),则返回 `false`。 

 7. 如果成功通过 CAS 操作增加工作线程数,则跳出内部循环,继续执行后续操作。 

 8. 在外部循环中,创建一个新的工作线程 `w`,并获取其对应的线程 `t`。 

 9. 如果线程 `t` 不为 `null`,则获取线程池的主锁 `mainLock`,并在主锁的保护下进行后续操作。  线程池的工作线程用过 Work类实现,通过ReentrantLock 锁保证线程安全

  1. 在主锁保护下,重新检查线程池的运行状态 `rs`。

  2. 如果运行状态小于 `SHUTDOWN`,或者运行状态为 `SHUTDOWN` 且 `firstTask` 为 `null`,则继续执行后续操作。

  3. 在主锁保护下,将新的工作线程 `w` 添加到工作线程列表 `workers` 中,并更新最大线程池大小 `largestPoolSize`。

  4. 最后,释放主锁,并判断是否成功添加了工作线程。

  5. 如果成功添加了工作线程,则启动线程 `t`。

  6. 如果添加工作线程失败,则调用 `addWorkerFailed` 方法进行后续处理。

16. 最后,返回是否成功启动了工作线程。

以上就是 线程池的实现。

线程池参数详解:

  1. corePoolSize(核心线程数):线程池中保持活动状态的最小线程数。当任务数量小于核心线程数时,线程池会优先创建新的线程来处理任务,而不是将任务放入队列中。默认情况下,核心线程数为0。

  2. maximumPoolSize(最大线程数):线程池中允许存在的最大线程数。当任务数量超过核心线程数且任务队列已满时,线程池会创建新的线程来处理任务,直到达到最大线程数。超过最大线程数的任务将根据线程池的拒绝策略进行处理。默认情况下,最大线程数为 Integer.MAX_VALUE

  3. keepAliveTime(线程空闲时间):当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务到来时的最长等待时间。超过这个时间,空闲线程将被终止并从线程池中移除。默认情况下,空闲线程的存活时间为60秒。

  4. unit(时间单位):用于指定 keepAliveTime 的时间单位,可以是秒、毫秒、微秒等。默认情况下,时间单位为秒。

  5. workQueue(任务队列):用于存放等待执行的任务的队列。线程池根据任务队列的容量来控制任务的排队和执行。常见的任务队列类型包括无界队列(如 LinkedBlockingQueue)和有界队列(如 ArrayBlockingQueue)。

  6. threadFactory(线程工厂):用于创建新线程的工厂类。线程池通过线程工厂来创建新的线程,并为线程设置名称、优先级等属性。

  7. handler(拒绝策略):当线程池无法接受新任务时,采取的处理策略。常见的拒绝策略包括抛出异常、丢弃任务、丢弃队列中最旧的任务等。

这些参数可以根据具体的需求和场景进行调整,以达到最佳的性能和资源利用效果。需要根据任务的特点、系统的负载情况和硬件资源等因素来选择合适的参数值。