两万字长文-最全的线程池详细介绍,是一篇全面深化的Java线程池讨论,详解Executors东西与ThreadPoolExecutor的常用办法,演示实战事例,并剖析ThreadPoolExecutor的各种参数与最佳实践。从基础到高档,为读者供给了丰富的线程池常识,既是API东西,也是面试宝典。不管您是初学者还是专业开发者,都将在这篇长文中找到深度解析与实用技巧,助您在多线程编程领域游刃有余。

一、 无界线程池 newCachedThreadPool

Java中Executor结构供给的一个工厂办法,用于创立一个依据需求创立新线程的线程池。这种线程池在履行使命之前会测验重用现有的闲暇线程,假如没有可用的线程,则会创立新的线程。假如线程在60秒内没有被运用,它将被中止并从缓存中移除

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        // 创立一个依据需求创立新线程的缓存线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 提交一些使命给线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模仿使命履行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 封闭线程池
        executorService.shutdown();
    }
}

创立了一个依据需求创立新线程的缓存线程池。然后,咱们提交了5个使命给线程池。因为线程池的特性是依据需求动态创立线程,所以这儿或许会创立新的线程来履行使命。假如线程在60秒内没有被运用,它将被中止并从缓存中移除。

缓存线程池适用于处理很多的短生命周期使命,因为它能够依据需求动态地调整线程池的巨细,防止了创立过多线程导致资源糟蹋的问题。

1.1 无界线程池的创立进程是可定制的

假如你想要自界说线程的创立办法,你能够运用 newCachedThreadPool 办法的重载版别,该版别承受一个 ThreadFactory 参数,答应你供给自界说的线程工厂

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class CachedThreadPoolWithCustomThreadFactory {
    public static void main(String[] args) {
        // 创立自界说线程工厂
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable);
                thread.setName("CustomThread-" + thread.getId());
                thread.setPriority(Thread.NORM_PRIORITY);
                thread.setDaemon(false);
                return thread;
            }
        };
        // 创立一个依据需求创立新线程的缓存线程池,运用自界说线程工厂
        var executorService = Executors.newCachedThreadPool(threadFactory);
        // 提交一些使命给线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模仿使命履行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 封闭线程池
        executorService.shutdown();
    }
}

经过供给自界说的 ThreadFactory 来完结线程的定制。在 ThreadFactory 的完结中,咱们设置了线程的称号、优先级和是否为看护线程。然后,咱们运用自界说的线程工厂创立了一个依据需求创立新线程的缓存线程池。这样就能够依照自己的需求创立线程

1.2 无界线程池的优缺陷

newCachedThreadPool 创立的线程池是一个无界线程池,它具有以下特点:

1.2.1 长处:

  1. 动态调整线程数量: 无界线程池会依据需求动态地创立新线程,无上限地习惯使命的数量。当有新使命提交时,假如池中没有闲暇线程,则会创立一个新线程来处理使命。这使得线程池能够灵敏地习惯作业负载的变化。

  2. 使命处理速度快: 因为能够依据需求创立新线程,无界线程池在瞬时负载较高的状况下能够更快地呼应使命。

1.2.2 缺陷:

  1. 或许导致资源耗尽: 因为线程数量没有上限,当有很多使命提交时,或许会创立很多线程,导致体系资源(如内存)耗尽。

  2. 或许导致过度竞赛: 在高并发状况下,很多线程的创立或许导致线程之间的竞赛,然后影响功能。

  3. 或许导致使命堆积: 假如使命的履行时刻较长,而新使命不断提交,或许导致线程池中积累很多未完结的使命,影响体系的安稳性。

  4. 不适用于长期运转的使命: 对于长期运转的使命,无界线程池或许会导致创立很多的线程,而这些线程在使命完结后不会被收回,最终或许耗尽体系资源。

无界线程池适用于使命短暂、处理速度快的场景,但在长期运转的使命或许负载较高的状况下,或许需求考虑其他类型的线程池,例如有界线程池或许运用使命行列进行使命排队。在挑选线程池类型时,需求依据详细运用场景和体系资源来进行权衡。

二、有界线程池 newFixedThreadPool

Java中Executor结构供给的一个工厂办法**,用于创立固定巨细的线程池**。这种线程池在运用程序的整个生命周期内都坚持相同数量的线程,当有新的使命提交时,假如线程池中的线程数未到达最大值,将会创立新的线程来处理使命,不然使命将会被放入行列等候履行

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        // 创立一个固定巨细为3的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 提交一些使命给线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模仿使命履行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 封闭线程池
        executorService.shutdown();
    }
}

创立了一个固定巨细为3的线程池。然后,咱们提交了5个使命给线程池。因为线程池的巨细是3,因而最多只能一同履行3个使命,而其他的使命将会被放入行列等候履行。线程池会在有闲暇线程时从行列中取出使命履行。

2.1 优缺陷

2.1.1 长处:

  1. 操控资源运用: 有界线程池限制了线程的数量,防止线程数量无限增加,然后有用操控了体系资源的运用。
  2. 防止资源耗尽: 因为线程数量是有限的,不会无限制地创立新线程。这有助于防止体系资源(如内存)被很多线程耗尽。
  3. 安稳性: 有界线程池能够更好地坚持体系的安稳性,防止过度竞赛和使命堆积。

2.1.2 缺陷:

  1. 灵敏性差: 有界线程池的线程数量是固定的,不能动态调整。假如负载较大,或许导致线程不足;假如负载较小,或许会糟蹋资源。
  2. 不适用于瞬时高并发: 在某些瞬时高并发的场景下,有界线程池或许无法及时呼应很多的使命,导致一些使命需求等候履行。
  3. 或许导致线程饥饿: 假如设置的线程数较小,而且使命提交速度较快,或许导致部分使命一向等候履行,产生线程饥饿的状况。

有界线程池适用于**相对安稳的作业负载,能够限制线程数量,防止资源耗尽。**在挑选线程池类型时,需求依据运用场景和功能需求来进行权衡。

三、 newSingleThreadExecutor

用于创立一个包含单个线程的线程池。这个线程池保证一切提交的使命依照次序履行,即每次只要一个线程在履行使命。假如这个线程因为反常而结束,会有另一个线程替代它,坚持线程池中一向存在一个活动线程。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorDemo {
    public static void main(String[] args) {
        // 创立一个包含单个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 提交一些使命给线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模仿使命履行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 封闭线程池
        executorService.shutdown();
    }
}

创立了一个包含单个线程的线程池,然后提交了5个使命给线程池。由所以单线程池,所以每次只要一个线程在履行使命,保证了使命的次序履行。这种线程池适用于需求按次序履行使命的场景,且在使命履行期间不需求并发履行。

3.1 优势

为什么只说优势?

这种单一线程的线程池运用场景是十分明确的,这儿只是想说明为什么存在单线程的线程池 是不是有种干嘛不直接运用线程的疑惑

运用 newSingleThreadExecutor 创立单线程池的主要优势在于对使命的次序履行以及反常处理的办理。以下是一些运用单线程池的优势:

  1. 次序履行: 单线程池保证使命依照提交的次序履行。这对于需求依照特定次序处理使命的场景十分有用,保证使命之间的次序联系得到保护。
  2. 线程复用: 单线程池中只要一个线程,该线程会被重复运用来履行不同的使命。这削减了线程的创立和毁掉开支,进步了线程的复用性。
  3. 反常办理: 假如使命抛出反常而导致线程中止,单线程池会创立一个新的线程来替代本来的线程,保证线程池中总是有一个可用的线程,防止因为反常而导致整个运用程序溃散。
  4. 方便的线程操控: 单线程池经过一个线程来处理使命,使得在某些场景下更容易进行线程操控和调试。例如,你能够更容易地追踪使命的履行,查看使命的日志,进行线程调试等。
  5. 使命行列的办理: 单线程池内部保护了一个使命行列,将使命依照提交的次序进行排队。这有助于办理使命的履行次序和操控使命的并发度。

虽然在某些简略的场景下直接运用一个线程或许足够,但单线程池的引进能够供给更多的操控和办理,使得在复杂的运用中更容易保护和调试。一同,线程池的运用也符合并发编程的最佳实践,能够有用地办理线程的生命周期,防止资源泄漏和糟蹋。

四、ThreadPoolExecutor

查看源码后会发现,上述工厂形式中的封装办法都是对ThreadPoolExecutor 进行封装。有点外观形式的滋味。

Executors 东西类供给了一些方便的办法,适用于许多常见的状况,而 ThreadPoolExecutor 则供给了更高度定制化的选项,适用于需求更精密操控的场景。在挑选运用哪个办法时,能够依据详细的需求和场景来决定。

ThreadPoolExecutor 是 Java 中 Executor 结构的底层完结,它供给了一个可灵敏配置的线程池。相比于 Executors 东西类供给的高档办法,ThreadPoolExecutor 答应你更详细地配置线程池的行为,包含中心线程数、最大线程数、线程存活时刻、使命行列等。

import java.util.concurrent.*;
public class CustomThreadPoolExecutorDemo {
    public static void main(String[] args) {
        // 创立 ThreadPoolExecutor
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,  // 中心线程数
                5,  // 最大线程数
                1,  // 线程闲暇时刻
                TimeUnit.SECONDS,  // 时刻单位
                new LinkedBlockingQueue<>(10)  // 使命行列
        );
        // 提交一些使命给线程池
        for (int i = 1; i <= 8; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 模仿使命履行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 封闭线程池
        executor.shutdown();
    }
}

经过 ThreadPoolExecutor 的构造办法自界说了线程池的各个参数,包含中心线程数、最大线程数、线程闲暇时刻、时刻单位和使命行列。这种办法答应更灵敏地配置线程池的行为,以习惯不同的运用场景。

ThreadPoolExecutor 供给了更多的配置选项,例如能够指定回绝战略、线程工厂等,然后满足更复杂的需求。需求留意的是,运用 ThreadPoolExecutor 需求更谨慎地处理线程池的参数,保证它们合理地配置以满足运用程序的功能和安稳性要求。

4.1 参数介绍

public ThreadPoolExecutor(
    int corePoolSize,          // 中心线程数:线程池中一向坚持存活的线程数,即便它们处于闲暇状况。
    int maximumPoolSize,       // 最大线程数:线程池中答应的最大线程数。当行列满了而且当时线程数小于最大线程数时,会创立新的线程来处理使命。
    long keepAliveTime,        // 线程闲暇时刻:非中心线程的闲暇时刻超越这个时刻,就会被收回。指定数值和时刻单位一同运用。
    TimeUnit unit,             // 时刻单位:指定 `keepAliveTime` 的时刻单位,例如 TimeUnit.SECONDS。
    BlockingQueue<Runnable> workQueue,   // 使命行列:用于保存等候履行的使命的堵塞行列。能够挑选不同类型的行列,如 LinkedBlockingQueue、ArrayBlockingQueue 等。
    ThreadFactory threadFactory,         // 线程工厂:用于创立新线程的工厂。能够经过它设置线程的称号、优先级、是否为看护线程等。
    RejectedExecutionHandler handler    // 回绝战略:当使命无法被提交履行时,会运用该战略来处理。例如,能够挑选默许的抛出反常、丢掉使命、调用调用者的线程来履行等。
)
  • 中心线程数(corePoolSize): 线程池中一向存活的线程数量,即便它们处于闲暇状况。中心线程会一向存在,不会因为使命的履行结束而被收回,除非设置了答应中心线程超时收回。
  • 最大线程数(maximumPoolSize): 线程池中答应的最大线程数量。当行列满了而且当时线程数小于最大线程数时,会创立新的线程来处理使命。
  • 线程闲暇时刻(keepAliveTime)和时刻单位(unit): 非中心线程的闲暇时刻超越这个时刻,就会被收回。指定数值和时刻单位一同运用。
  • 使命行列(workQueue): 用于保存等候履行的使命的堵塞行列。不同类型的行列有不同的特性,如 LinkedBlockingQueue 是无界行列,而 ArrayBlockingQueue 是有界行列。
  • 线程工厂(threadFactory): 用于创立新线程的工厂,答应对线程的创立进行定制。能够设置线程的称号、优先级、是否为看护线程等。
  • 回绝战略(handler): 当使命无法被提交履行时,会运用该战略来处理。例如,能够挑选默许的抛出反常、丢掉使命、调用调用者的线程来履行等。

4.2 BlockingQueue <Runnable> (后面单独学习 内容较多)

BlockingQueue<Runnable> 是用于存储待履行使命的堵塞行列。Java 中供给了多种完结 BlockingQueue 接口的行列,其间常用的包含 LinkedBlockingQueueArrayBlockingQueueSynchronousQueue 等。下面简要介绍这三种行列:

  1. LinkedBlockingQueue: 运用链表完结的无界行列,理论上能够无限制地增加元素。在没有指定容量时,默许巨细为 Integer.MAX_VALUE。适用于使命生产速度和消费速度差异较大的场景。

    
    // 无界行列
    BlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<>();
    
  2. ArrayBlockingQueue: 运用数组完结的有界行列,需求指定行列的容量。适用于固定巨细的线程池,能够防止资源耗尽。

    
    // 有界行列,指定容量为 10
    BlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
    
  3. SynchronousQueue: 一个不存储元素的行列。每个插入操作都必须等候另一个线程的对应移除操作,反之亦然。适用于使命提交和履行一一对应的场景,如直接将使命交给线程履行。

    // 不存储元素的行列
    BlockingQueue<Runnable> synchronousQueue = new SynchronousQueue<>();
    

这些行列的挑选取决于运用程序的需求和功能特性。无界行列适合使命生产速度大于消费速度的场景,但或许导致内存耗尽。有界行列适合资源受限或使命生产和消费速度相近的场景。SynchronousQueue 适用于使命提交和履行一一对应的场景,但或许导致较高的线程创立和毁掉开支。在挑选行列时,需求依据详细的运用场景进行权衡。

4.3 办法shutdown()和shutdownNow()

shutdown()shutdownNow()ThreadPoolExecutor 中用于封闭线程池的两个办法。

4.3.1 shutdown()

shutdown() 办法用于优雅地封闭线程池。调用这个办法后,线程池将不再承受新的使命提交,但会持续履行现已提交的使命,直到一切使命完结。之后,线程池将封闭,释放占用的资源。

ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
// 封闭线程池
executor.shutdown();

4.3.2 shutdownNow()

shutdownNow() 办法用于当即封闭线程池。调用这个办法后,线程池会测验中止一切正在履行的使命,并回来等候履行的使命列表。该办法回来的列表包含一切未履行的使命,或许包含现已提交但未开端履行的使命以及正在履行的使命。

ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
// 当即封闭线程池
List<Runnable> unexecutedTasks = executor.shutdownNow();
4.3.2.1 shutdownNow() 办法回来一个 List<Runnable>

shutdownNow() 办法回来一个 List<Runnable>,该列表包含测验中止履行的一切未完结使命。详细来说,回来的列表包含以下两类使命:

  1. 现已提交但没有开端履行的使命。
  2. 正在履行的使命,但被测验中止。

回来的列表答应你查看在封闭线程池时未能正常完结的使命,或许包含部分或全部使命。这对于记载或处理这些未完结的使命信息是有用的。

以下是一个简略的示例,演示了如何运用 shutdownNow() 办法并查看未完结使命:

import java.util.List;
import java.util.concurrent.*;
public class ShutdownNowExample {
    public static void main(String[] args) {
        // 创立一个线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        // 提交一些使命给线程池
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(5000); // 模仿使命履行,这儿设置一个较长的履行时刻
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 封闭线程池,获取未完结使命列表
        List<Runnable> uncompletedTasks = executor.shutdownNow();
        // 打印未完结使命的信息
        System.out.println("Number of uncompleted tasks: " + uncompletedTasks.size());
    }
}

在上面的比如中,线程池提交了5个使命,但因为设置了较长的使命履行时刻,线程池在调用 shutdownNow() 办法时或许会有未完结的使命。经过查看回来的 List<Runnable>,咱们能够获取未完结使命的信息,例如未完结使命的数量。

4.3.4 联系和留意事项

  • shutdown()shutdownNow() 都会中止线程池的承受新使命的能力。
  • shutdown() 是一种陡峭的封闭办法,它等候正在履行的使命完结,然后封闭线程池。这个办法回来后,能够保证没有新使命被承受,且一切已提交的使命都现已履行结束。
  • shutdownNow() 是一种当即封闭办法,它测验中止一切正在履行的使命,回来未履行的使命列表。这个办法或许会导致一些使命未能履行完结。
  • 在某些状况下,shutdownNow() 回来的未履行使命列表或许为空。详细的行为取决于线程池的完结和使命履行的状况。
  • 运用这两个办法后,线程池将不能再承受新使命,假如需求从头运用线程池,需求从头创立一个新的线程池实例。

挑选运用哪个办法取决于你的运用场景和封闭线程池的需求。
shutdown()shutdownNow() 以及线程的中止(interrupt)之间存在一些相关,下面别离解释它们的联系:

4.3.5 shutdown() 办法和中止:

  • shutdown() 办法不会直接中止线程,而是中止承受新使命,并等候现已提交的使命履行完结。这是一种陡峭的封闭办法,不会强制中止线程。

  • 当调用 shutdown() 后,线程池会测验履行一切已提交的使命,等候它们完结。假如某个使命在履行进程中被堵塞(如等候 I/O 操作、获取锁等),线程池将等候这些使命完结。因而,shutdown() 并不会中止线程,而是等候一切使命完结。

4.3.6 shutdownNow() 办法和中止:

  • shutdownNow() 办法测验当即中止履行使命,并回来未完结的使命列表。这个办法会测验中止正在履行的使命。假如使命的 run() 办法呼应中止,使命会被中止;不然,使命或许持续履行。

  • 回来的未完结使命列表包含那些现已提交但未开端履行的使命以及测验中止的正在履行的使命。对于正在履行的使命,假如使命未呼应中止,它或许不会被彻底中止。

4.3.7 中止和线程的中止:

  • 中止是一种线程间的协作机制,经过调用 Thread.interrupt() 办法来告诉线程中止。线程能够经过查看中止状况来呼应中止。

  • 假如线程池中的使命完结了对中止的呼应,那么在线程池调用 shutdownNow() 时,测验中止使命时或许会调用使命的 interrupt() 办法。

  • 线程池中的使命在履行时,能够经过查看 Thread.interrupted()isInterrupted() 办法来检测中止状况,并相应地处理中止恳求。

总体来说,shutdown()shutdownNow() 并不直接中止线程,但线程池中的使命能够经过中止机制来呼应封闭恳求。在使命的履行中,要留意捕获中止反常并恰当地处理中止恳求。

4.3.8 isShutdown

isShutdown() 办法是 ExecutorService 接口的一个办法,用于判别线程池是否现已调用过 shutdown() 办法。详细来说,它用于查看线程池是否现已处于封闭状况。

boolean isShutdown();

回来值是一个布尔值,假如线程池现已调用了 shutdown() 办法,回来 true;不然,回来 false

运用示例:

ExecutorService executorService = Executors.newFixedThreadPool(5);
// ...
// 判别线程池是否现已封闭
if (executorService.isShutdown()) {
    System.out.println("The thread pool is already shut down.");
} else {
    System.out.println("The thread pool is still active.");
}
// ...
// 封闭线程池
executorService.shutdown();

在上面的比如中,经过调用 isShutdown() 办法,咱们能够在封闭线程池之前查看它的状况。这在某些场景下或许是有用的,例如在履行封闭操作之前查看线程池是否现已封闭,以防止重复封闭的问题。

4.3.9 isTerminating()isTerminated()

isTerminating()isTerminated()ExecutorService 接口供给的办法,用于查询线程池的中止状况。

1. isTerminating()
boolean isTerminating();

isTerminating() 办法用于判别线程池是否处于中止中。回来值是一个布尔值,假如线程池现已调用了 shutdown() 办法且至少有一个使命还在履行或等候履行,回来 true;不然,回来 false

2. isTerminated()
boolean isTerminated();

isTerminated() 办法用于判别线程池是否现已彻底中止。回来值是一个布尔值,假如线程池现已调用了 shutdown() 办法而且一切使命都现已履行结束(包含已完结和正在履行的使命),回来 true;不然,回来 false

这两个办法能够用于监视线程池的中止状况。以下是一个示例:

ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交一些使命给线程池
for (int i = 1; i <= 5; i++) {
    final int taskId = i;
    executorService.submit(() -> {
        System.out.println("Task " + taskId + " is executing by " + Thread.currentThread().getName());
        try {
            Thread.sleep(2000); // 模仿使命履行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}
// 封闭线程池
executorService.shutdown();
// 等候一段时刻,让线程池履行使命
try {
    Thread.sleep(3000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
// 判别线程池的中止状况
System.out.println("Terminating: " + executorService.isTerminating());
System.out.println("Terminated: " + executorService.isTerminated());

在上面的比如中,咱们提交了一些使命给线程池,然后封闭线程池。在封闭后,经过 isTerminating()isTerminated() 办法来查看线程池的中止状况。在等候一段时刻后,能够观察到 isTerminating() 回来 true,表明线程池正在中止中,而 isTerminated() 回来 false,表明线程池没有彻底中止。

4.3.10 awaitTermination(long timeout,TimeUnit unit)

awaitTermination(long timeout, TimeUnit unit)ExecutorService 接口供给的办法之一。它用于堵塞当时线程,等候线程池中的一切使命履行结束,或许等候指定的超时时刻。

boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
  • timeout:等候的超时时刻。
  • unit:超时时刻的单位。

这个办法回来一个布尔值,表明是否在指定的超时时刻内线程池中的一切使命都现已履行结束。假如在超时时刻内使命履行结束,回来 true;不然,回来 false

运用示例:

ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交一些使命给线程池
// 封闭线程池
executorService.shutdown();
try {
    // 等候线程池中的使命履行结束,或许超时时刻到达
    if (executorService.awaitTermination(10, TimeUnit.SECONDS)) {
        System.out.println("All tasks have completed.");
    } else {
        System.out.println("Timeout reached. Some tasks might still be running.");
    }
} catch (InterruptedException e) {
    System.err.println("Interrupted while waiting for termination.");
    Thread.currentThread().interrupt(); // 从头设置中止状况
}

在这个比如中,awaitTermination(10, TimeUnit.SECONDS) 会堵塞当时线程,等候线程池中的一切使命履行结束或许超越10秒超时时刻。假如在超时时刻内使命都履行结束,回来 true;不然,回来 false

留意:

  • 在调用 shutdown() 后,建议运用 awaitTermination() 来等候使命的完结,以保证线程池现已彻底封闭。
  • 假如在等候进程中当时线程被中止,awaitTermination() 办法会抛出 InterruptedException 反常,并需求恰当处理中止状况。

4.3.11 工厂ThreadFactory+Thread+UncaughtExceptionHandler处理反常

能够经过完结 ThreadFactory 接口和设置 UncaughtExceptionHandler 来自界说线程工厂和处理线程反常的办法

以下是一个示例,演示如何运用 ThreadFactoryUncaughtExceptionHandler 处理线程反常:

import java.util.concurrent.ThreadFactory;
public class CustomThreadFactoryExample {
    public static void main(String[] args) {
        // 创立自界说线程工厂
        ThreadFactory threadFactory = new CustomThreadFactory();
        // 运用自界说线程工厂创立线程
        Thread thread = threadFactory.newThread(() -> {
            System.out.println("Thread is running.");
            throw new RuntimeException("Simulated Exception");
        });
        // 设置线程的反常处理器
        thread.setUncaughtExceptionHandler((t, e) ->
                System.err.println("Uncaught exception in thread " + t.getName() + ": " + e.getMessage()));
        // 发动线程
        thread.start();
    }
}
class CustomThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        // 创立线程时,设置线程的称号和反常处理器
        Thread thread = new Thread(r);
        thread.setName("CustomThread-" + thread.getId());
        thread.setUncaughtExceptionHandler((t, e) ->
                System.err.println("Uncaught exception in thread " + t.getName() + ": " + e.getMessage()));
        return thread;
    }
}

在这个比如中:

  1. 创立了一个完结了 ThreadFactory 接口的 CustomThreadFactory 类,用于创立新的线程。
  2. CustomThreadFactory 中,经过 newThread 办法创立新线程时,设置了线程的称号和反常处理器。
  3. 创立线程时,经过 thread.setUncaughtExceptionHandler 办法设置了线程的反常处理器。
  4. 线程运转时,当产生反常时,反常将由设置的反常处理器进行处理。

这样,经过自界说线程工厂和反常处理器,能够更灵敏地处理线程的创立和反常状况。

4.3.12 set(get)RejectedExecutionHandler()

在 Java 的 ThreadPoolExecutor 中,能够运用 setRejectedExecutionHandler() 办法设置回绝战略(RejectedExecutionHandler),而运用 getRejectedExecutionHandler() 办法获取当时线程池的回绝战略。

1. setRejectedExecutionHandler() 办法
void setRejectedExecutionHandler(RejectedExecutionHandler handler);

此办法用于设置线程池的回绝战略。回绝战略界说了当线程池无法承受新使命时的行为。常见的回绝战略包含抛出反常、直接丢掉使命、将使命增加到调用线程中履行等。

示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2,  // corePoolSize
        5,  // maximumPoolSize
        1,  // keepAliveTime
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(10) // workQueue
);
// 设置自界说的回绝战略
executor.setRejectedExecutionHandler(new MyRejectedExecutionHandler());
2. getRejectedExecutionHandler() 办法
RejectedExecutionHandler getRejectedExecutionHandler();

此办法用于获取当时线程池的回绝战略。

示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2,  // corePoolSize
        5,  // maximumPoolSize
        1,  // keepAliveTime
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(10) // workQueue
);
// 获取当时线程池的回绝战略
RejectedExecutionHandler handler = executor.getRejectedExecutionHandler();

需求留意的是,当线程池处于封闭状况时,setRejectedExecutionHandler() 将抛出 RejectedExecutionException,因而最好在创立线程池后,但在调用 shutdown() 之前进行设置。

自界说回绝战略需求完结 RejectedExecutionHandler 接口,例如:

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class MyRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自界说回绝战略的处理逻辑
        System.err.println("Task rejected: " + r.toString());
    }
}

在上述示例中,MyRejectedExecutionHandler 完结了 RejectedExecutionHandler 接口,当使命被回绝时,会打印一条错误信息。您能够依据实践需求完结不同的回绝战略。

4.3.13

ThreadPoolExecutor 中,allowsCoreThreadTimeOutallowCoreThreadTimeOut(boolean) 办法用于操控中心线程是否答应超时收回。

1. allowsCoreThreadTimeOut 办法
boolean allowsCoreThreadTimeOut();

此办法用于查询线程池是否答应中心线程超时收回。假如回来 true,则中心线程在闲暇时会依据 keepAliveTime 进行超时收回。假如回来 false,则中心线程将一向坚持存活,不会超时收回。

2. allowCoreThreadTimeOut(boolean) 办法
void allowCoreThreadTimeOut(boolean value);

此办法用于设置线程池是否答应中心线程超时收回。假如参数 valuetrue,则答应中心线程超时收回;假如为 false,则不答应中心线程超时收回。

示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2,  // corePoolSize
        5,  // maximumPoolSize
        1,  // keepAliveTime
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(10) // workQueue
);
// 查询线程池是否答应中心线程超时收回
boolean allowsCoreThreadTimeOut = executor.allowsCoreThreadTimeOut();
// 设置线程池答应中心线程超时收回
executor.allowCoreThreadTimeOut(true);

在上述示例中,经过 allowsCoreThreadTimeOut() 能够查询线程池是否答应中心线程超时收回,而经过 allowCoreThreadTimeOut(true) 能够设置线程池答应中心线程超时收回。

中心线程超时收回的典型运用场景是,在使命提交量较低且需求节约资源时,能够答应中心线程在闲暇一段时刻后自动收回,以削减线程池的资源占用。

4.3.14 prestartCoreThread()prestartAllCoreThreads()

ThreadPoolExecutor 中,prestartCoreThread()prestartAllCoreThreads() 办法用于预发动中心线程,即在发动线程池时创立一些中心线程,以便更早地呼应使命的履行。

1. prestartCoreThread() 办法
boolean prestartCoreThread();

此办法用于测验发动一个中心线程。假如成功发动了一个中心线程,则回来 true;假如无法发动中心线程(例如,因为线程数已到达中心线程数上限),则回来 false

示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2,  // corePoolSize
        5,  // maximumPoolSize
        1,  // keepAliveTime
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(10) // workQueue
);
// 测验发动一个中心线程
boolean started = executor.prestartCoreThread();
2. prestartAllCoreThreads() 办法
int prestartAllCoreThreads();

此办法用于发动一切未发动的中心线程,并回来发动的线程数量。假如线程池中的中心线程现已发动,或许因为某些原因无法发动,则回来 0

示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2,  // corePoolSize
        5,  // maximumPoolSize
        1,  // keepAliveTime
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(10) // workQueue
);
// 发动一切未发动的中心线程
int startedThreads = executor.prestartAllCoreThreads();

在上述示例中,经过 prestartCoreThread() 能够测验发动一个中心线程,而经过 prestartAllCoreThreads() 能够发动一切未发动的中心线程。

这两个办法能够在创立线程池后手动预发动一些中心线程,以进步线程池对使命的呼应速度。

4.3.15 afterExecute()和beforeExecute()

ThreadPoolExecutor 中,beforeExecute(Thread t, Runnable r)afterExecute(Runnable r, Throwable t) 是两个钩子办法(hook methods),它们答应你在使命履行前后履行一些额定的操作。

1. beforeExecute(Thread t, Runnable r)
protected void beforeExecute(Thread t, Runnable r) {
    // 在使命履行前履行的操作
}

此办法在履行每个使命之前被调用。它供给了履行使命的线程和要履行的使命作为参数。你能够经过重写这个办法来履行一些初始化或日志记载等操作。

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(capacity)
) {
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        // 在使命履行前履行的操作
        System.out.println("Before executing task: " + r.toString());
    }
};
2. afterExecute(Runnable r, Throwable t)
protected void afterExecute(Runnable r, Throwable t) {
    // 在使命履行后履行的操作
}

此办法在使命履行完结后被调用。它供给了履行完的使命和或许的反常作为参数。你能够经过重写这个办法来履行一些收尾作业,例如资源清理、日志记载、功能计算等操作。

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(capacity)
) {
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        // 在使命履行后履行的操作
        System.out.println("After executing task: " + r.toString());
        if (t != null) {
            System.err.println("Task ended with an exception: " + t.getMessage());
        }
    }
};

经过这两个钩子办法,你能够在使命履行前后插入自界说逻辑,对线程池的行为进行定制化。这能够用于调试、功能监控、反常处理等方面。留意,这两个办法是在使命履行的同一线程中调用的,因而对线程安全性要有必定的考虑。

4.3.16 remove(Runnable)

ThreadPoolExecutor 中,remove(Runnable) 办法用于测验从作业行列中移除一个指定的使命,而不是从正在履行的使命中移除。这个办法能够用于取消等候履行的使命。

boolean remove(Runnable task);
  • task:要从行列中移除的使命。

回来值为 true 表明成功从行列中移除使命,false 表明使命不在行列中或现已被履行。

运用 remove(Runnable) 办法时需求留意以下几点:

  1. 只能从行列中移除还未开端履行的使命。 假如使命现已在履行,remove(Runnable) 办法将回来 false

  2. 不会中止正在履行的使命。 即便成功从行列中移除了使命,正在履行的使命仍会持续履行。

下面是一个简略的示例:

import java.util.concurrent.*;
public class ThreadPoolRemoveExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,  // corePoolSize
                5,  // maximumPoolSize
                1,  // keepAliveTime
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10) // workQueue
        );
        // 提交使命
        Runnable task1 = () -> System.out.println("Task 1 is executing.");
        Runnable task2 = () -> {
            try {
                Thread.sleep(5000);  // 模仿长期使命
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task 2 is executing.");
        };
        executor.submit(task1);
        Future<?> future = executor.submit(task2);
        // 移除等候履行的使命
        boolean removed = executor.remove(task1);
        System.out.println("Task 1 removed: " + removed);
        // 移除正在履行的使命(不会中止履行中的使命)
        removed = executor.remove(task2);
        System.out.println("Task 2 removed: " + removed);
        // 等候使命2履行结束
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        // 封闭线程池
        executor.shutdown();
    }
}

在上述示例中,使命1被成功移除,因为它还未开端履行。而使命2在履行中,即便调用了 remove() 办法,使命2仍会持续履行,不会被中止。这强调了 remove(Runnable) 办法的一些限制。

4.3.17 ThreadPoolExecutor的回绝战略

ThreadPoolExecutor 在面对无法承受新使命的状况时,会采取回绝战略(RejectedExecutionHandler)。回绝战略界说了在线程池到达最大容量且作业行列已满时,应该采取的行为。以下是 ThreadPoolExecutor 供给的几种回绝战略:

1. AbortPolicy(默许战略)

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(capacity),
        new ThreadPoolExecutor.AbortPolicy()
);

AbortPolicy 是默许的回绝战略。当作业行列已满且线程池到达最大容量时,新使命将被直接回绝,并抛出 RejectedExecutionException 反常。

2. CallerRunsPolicy

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(capacity),
        new ThreadPoolExecutor.CallerRunsPolicy()
);

CallerRunsPolicy 战略会直接在调用者线程中履行被回绝的使命。这意味着当线程池无法承受新使命时,使命将在调用 execute 的线程中履行。

3. DiscardPolicy

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(capacity),
        new ThreadPoolExecutor.DiscardPolicy()
);

DiscardPolicy 战略会默默地丢掉被回绝的使命,不会抛出反常也不会履行使命。这或许导致某些使命被疏忽。

4. DiscardOldestPolicy

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(capacity),
        new ThreadPoolExecutor.DiscardOldestPolicy()
);

DiscardOldestPolicy 战略会丢掉行列中等候时刻最长的使命,然后测验将新使命加入行列。

自界说回绝战略

除了上述几种内置的回绝战略,你还能够经过完结 RejectedExecutionHandler 接口来自界说回绝战略。例如:

public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自界说的回绝战略逻辑
        System.err.println("Task rejected: " + r.toString());
    }
}

然后在创立线程池时运用自界说的回绝战略:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maximumPoolSize,
        keepAliveTime,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(capacity),
        new CustomRejectedExecutionHandler()
);

经过挑选恰当的回绝战略,能够更好地应对线程池无法承受新使命的状况。