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类进行自定义线程池配置。
相关文章
|
7月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
281 0
|
6月前
|
存储 安全 Java
2025 年一线互联网大厂最新高质量 Java 面试八股文整理及答案汇总
本文整理了一线互联网大厂最新的高质量Java面试八股文及其答案,涵盖Java基础、集合框架与多线程三大核心模块。内容包括面向对象与面向过程的区别、`equals`与`==`的差异、`final`和`static`的用法、集合类如`ArrayList`与`LinkedList`的对比、`HashMap`的工作原理及其与`Hashtable`的区别,以及多线程中的线程创建方式、生命周期、上下文切换和死锁等知识点。通过系统化的梳理与解析,帮助读者高效备考Java面试,掌握核心技术要点。资源可从文末链接下载。
1109 40
|
5月前
|
存储 算法 安全
JAVA 八股文全网最详尽整理包含各类核心考点助你高效学习 jAVA 八股文赶紧收藏
本文整理了Java核心技术内容,涵盖Java基础、多线程、JVM、集合框架等八股文知识点,包含面向对象特性、线程创建与通信、运行时数据区、垃圾回收算法及常用集合类对比,附有代码示例与学习资料下载链接,适合Java开发者系统学习与面试准备。
1210 0
|
10月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
479 60
【Java并发】【线程池】带你从0-1入门线程池
|
6月前
|
存储 安全 Java
2025 年一线互联网大厂最新高质量 Java 面试八股文整理带答案及实操要点
本文整理了一线互联网大厂最新的高质量Java面试八股文及答案,涵盖Java基础、集合、多线程等多个核心方面,帮助你高效备考。内容包括面向对象与面向过程的区别、`equals`与`==`的对比、`final`和`static`的用法,以及ArrayList与LinkedList的区别、HashMap的工作原理等。同时,深入探讨了多线程创建方式、生命周期、上下文切换及死锁问题,并附有实操代码示例。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
1159 1
|
9月前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
486 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
10月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
342 0
探索Java动态代理的奥秘:JDK vs CGLIB
|
11月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
405 17
|
12月前
|
安全 Java 程序员
ArrayList vs Vector:一场线程安全与性能优化的世纪之争!
在 Java 面试中,ArrayList 和 Vector 是高频考点,但很多人容易混淆。本文通过10分钟深入解析它们的区别,帮助你快速掌握性能、线程安全性、扩容机制等核心知识,让你轻松应对面试题目,提升自信!
304 18