Java线程池,白话文vs八股文,原来是这么回事!

简介: 一、线程池原理1、白话文篇1.1、正式员工(corePoolSize)正式员工:这些是公司最稳定和最可靠的长期员工,他们一直在工作,不会被解雇或者辞职。他们负责处理公司的核心业务,比如生产、销售、财务等。在Java线程池中,正式员工对应于核心线程(corePoolSize),这些线程会一直存在于线程池中。他们负责执行线程池中的任务,如果没有任务,他们会等待新的任务到来。1.2、所有员工(maximumPoolSize)所有员工:这些是公司所有的员工,包括正式员工和外包员工。他们共同组成了公司的团队,协作完成公司的各种业务。在Java线程池中,所有员工对应于所有线程(maxim

一、线程池原理

1、白话文篇

1.1、正式员工(corePoolSize)

正式员工:这些是公司最稳定和最可靠的长期员工,他们一直在工作,不会被解雇或者辞职。他们负责处理公司的核心业务,比如生产、销售、财务等。在Java线程池中,正式员工对应于核心线程(corePoolSize),这些线程会一直存在于线程池中。他们负责执行线程池中的任务,如果没有任务,他们会等待新的任务到来。

1.2、所有员工(maximumPoolSize)

所有员工:这些是公司所有的员工,包括正式员工和外包员工。他们共同组成了公司的团队,协作完成公司的各种业务。在Java线程池中,所有员工对应于所有线程(maximumPoolSize),这些线程是线程池能够创建的最大数量的线程。他们都可以执行线程池中的任务,如果没有任务,他们会等待新的任务到来。

1.3、外包员工(maximumPoolSize - corePoolSize)

外包员工:这些是公司根据业务需求临时雇佣的员工,他们只在有额外的任务时才会被雇佣,如果没有任务,他们会被解雇或者辞职。他们也可以负责处理公司的业务,比如活动、项目、临时需求等。在Java线程池中,外包员工对应于非核心线程(maximumPoolSize - corePoolSize),这些线程只在核心线程不足以处理所有任务时才会被创建,他们也负责执行线程池中的任务,如果没有任务,他们会等待新的任务到来。

1.4、排队任务(workQueue)

排队任务:这是公司用来存放待处理任务的地方,比如订单、合同、报告等。每个任务都有一个优先级和一个截止日期,根据这些信息来决定哪个任务先处理,哪个任务后处理。在Java线程池中,任务队列对应于BlockingQueue,这是一个用来存放Runnable对象的阻塞队列。每个Runnable对象都代表一个要执行的任务,根据队列的类型和容量来决定哪个任务先入队,哪个任务后入队。当有空闲的线程时,它会从队列中取出一个任务来执行。

1.5、拒绝策略(handler)

拒绝策略:这是公司用来处理无法接受或者无法完成的任务的方法也会选择相应的放弃和别的策略,比如退单、转单、延期等。每个策略都有一个风险和一个收益,根据公司的目标和资源来选择合适的策略。在Java线程池中,拒绝策略对应于RejectedExecutionHandler,这是一个用来处理无法执行的任务的接口。每个实现类都代表一个不同的策略,根据线程池的状态和参数来选择合适的策略。

2、八股文篇

一个线程提交到线程池的处理流程如下图

1)初始化线程池,线程池初始化时并没有创建corePoolSize数目的核心线程,而是惰性加载的方式。等有任务后才创建核心线程。

2)如果线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的核心线程来处理被添加的任务。

3)如果线程池中的数量大于等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。

4)如果线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的非核心线程来处理被添加的任务。

5)如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

二、线程池实现

2.1、ThreadPoolExecutor线程池(推荐)

public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        // 创建一个线程池对象
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, 5, 10, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
        // 提交多个任务到线程池中
        for (int i = 1; i <= 10; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " is running");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 关闭线程池
        executor.shutdown();
    }
}

该示例创建了一个核心线程数为 2,最大线程数为 5,等待队列大小为 5 的线程池对象,然后提交了 10 个任务到线程池中。每个任务会休眠 1 秒钟,然后输出当前线程的名称。最后,调用 shutdown() 方法关闭线程池。

当线程池任务处理不过来的时候,可以通过handler指定的策略进行处理,ThreadPoolExecutor提供了四种策略:

1)
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;也是默认的处理方式。

2)
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

3)
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

4)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

可以通过实现RejectedExecutionHandler接口自定义处理方式。

2.2、Executors线程池(不推荐)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池对象
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // 提交多个任务到线程池中
        for (int i = 1; i <= 10; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " is running");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 关闭线程池
        executor.shutdown();
    }
}

该示例使用 Executors 工厂类创建了一个固定大小为 3 的线程池对象,然后提交了 10 个任务到线程池中。每个任务会休眠 1 秒钟,然后输出当前线程的名称。最后,调用 shutdown() 方法关闭线程池。

需要注意的是,虽然 Executors 提供了许多快速创建线程池对象的方法,但是这些方法并不能满足所有的需求和场景,因此在实际应用中,需要根据具体情况和性能需求选择合适的线程池实现,并进行适当的参数设置和优化等操作。以下是几种创建方式:

1)
Executors.newCachedThreadPool();

说明: 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.

内部实现:new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue());

2)
Executors.newFixedThreadPool(int);

说明: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

内部实现:new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

3)
Executors.newSingleThreadExecutor();

说明:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照顺序执行。

内部实现:new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())

4)
Executors.newScheduledThreadPool(int);

说明:创建一个定长线程池,支持定时及周期性任务执行。

内部实现:new
ScheduledThreadPoolExecutor(corePoolSize)

【附】阿里巴巴Java开发手册中对线程池的使用规范

2.3、相关参数说明

2.3.1、线程池的等待队列

1)ArrayBlockingQueue: 这是一个由数组实现的容量固定的有界阻塞队列.

2)SynchronousQueue: 没有容量,不能缓存数据;每个put必须等待一个take; offer()的时候如果没有另一个线程在poll()或者take()的话返回false。

3)LinkedBlockingQueue: 这是一个由单链表实现的默认无界的阻塞队列。LinkedBlockingQueue提供了一个可选有界的构造函数,而在未指明容量时,容量默认为Integer.MAX_VALUE。

队列操作:

2.3.2、线程数值

Oracle 官方并没有给出线程池 corePoolSize 的具体参考值,因为这个值的大小应该根据实际业务场景和系统资源情况来进行优化调整。不同的业务场景和系统资源状况可能需要不同的 corePoolSize 设置。

不过,在《Java并发编程实战》一书中给出了建议。 在这本书中,作者 Brian Goetz 等人指出,线程池的规模应该根据任务类型和计算密集度来确定。

  • 对于 CPU 密集型任务,应该将核心线程数设置为处理器核心数加 1 或者 2;
  • 对于 I/O 密集型任务,可以适当增加核心线程数以利用空闲的 CPU 时间。具体大小可以根据实际情况进行调整,建议设置在 2 x N 左右,其中 N 是可用 CPU 核心数。

这个建议是基于以下考虑:对于 CPU 密集型任务,线程需要大量计算,因此需要足够多的 CPU 资源,而处理器核心数加 1 或者 2 的数量可以充分利用 CPU 资源,避免线程之间的竞争和阻塞;而对于 I/O 密集型任务,由于线程大部分时间都处于等待 I/O 操作的状态,因此可以适当增加核心线程数以利用空闲的 CPU 时间,从而提高系统效率。虽然这个建议并非官方标准,但在实际应用中已经得到广泛的认可和应用,并取得了不错的效果。

三、相关题目

  1. 什么是Java线程池?它的作用是什么?

答:Java线程池是一种用于管理和重用线程的机制。它的主要作用是减少线程创建和销毁的开销,提高线程的重用性,以优化多线程应用的性能。

  1. Java线程池的核心参数是什么?请解释它们的含义。

答:Java线程池的核心参数包括:

  • 核心线程池大小(Core Pool Size):线程池中保持活动的最小线程数量。
  • 最大线程池大小(Maximum Pool Size):线程池允许的最大线程数量。
  • 任务队列(work Queue):用于存储等待执行的任务的数据结构。
  • 线程存活时间(Thread Keep-Alive Time):在没有任务时,超过核心线程数的线程保持存活的时间。
  1. Java线程池中的任务执行顺序是什么样的?

答:任务执行顺序可以根据任务提交方式和线程池类型来变化。通常,线程池会按照任务提交的顺序执行任务,但线程池类型不同,如FixedThreadPool和SingleThreadPool,可能会有不同的执行策略。对于有优先级的任务,线程池可以根据任务优先级来决定执行顺序。

  1. 什么是线程池的拒绝策略(Rejection Policy)?

答:拒绝策略定义了当任务队列已满且线程池无法创建更多线程时,如何处理新提交的任务。Java线程池提供了几种内置的拒绝策略,如AbortPolicy(默认,拒绝并抛出异常)、CallerRunsPolicy(调用者线程执行任务)、DiscardPolicy(默默丢弃任务)和DiscardOldestPolicy(丢弃队列中最老的任务)。

  1. Java中有哪些线程池实现类,它们有什么不同?

答:Java中有多种线程池实现类,包括:

  • FixedThreadPool:固定大小的线程池,核心线程数和最大线程数相等,无任务队列。 CachedThreadPool:根据需要创建新线程的线程池,适用于处理大量短生命周期的任务。 SingleThreadPool:只包含一个线程的线程池,用于按顺序执行任务。 ScheduledThreadPool:用于定时或周期性执行任务的线程池。 自定义线程池:可以通过ThreadPoolExecutor类进行自定义线程池配置。
相关文章
|
1月前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
2月前
|
存储 缓存 安全
HashMap VS TreeMap:谁才是Java Map界的王者?
HashMap VS TreeMap:谁才是Java Map界的王者?
97 2
|
2月前
|
数据采集 缓存 Java
Python vs Java:爬虫任务中的效率比较
Python vs Java:爬虫任务中的效率比较
|
4月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
129 1
|
17天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
14天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
1月前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
112 38
|
21天前
|
存储 缓存 监控
Java中的线程池深度解析####
本文深入探讨了Java并发编程中的核心组件——线程池,从其基本概念、工作原理、核心参数解析到应用场景与最佳实践,全方位剖析了线程池在提升应用性能、资源管理和任务调度方面的重要作用。通过实例演示和性能对比,揭示合理配置线程池对于构建高效Java应用的关键意义。 ####
|
1月前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
85 4
|
1月前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
243 2