线程池你真不来了解一下吗?

京东五面过了之后,发现掌握了这些技术,面试其实真不难 前言 作为一个执着于分享技术的小编,终于有群友力战京东并成功斩获 Offer,接下来就把面试京东五面的经验分享给大家,希望大家能站在巨人的肩膀上,更上一层楼,如果文章对你有帮助,还请多多关注支持哦 京东 Java 一面 HashMap 和 HashTable 以及 CocurrentHashMap 详细说明 网络 IO 模型?什么是多路复用 IO?select 和 epoll 的差别? TCP 三次握手的过程,如果没有第三次握手有什么问题? .. 阅读详情

前言

只有光头才能变强

回顾前面:

本篇主要是讲解线程池,这是我在多线程的倒数第二篇了,后面还会有一篇死锁。主要将多线程的基础过一遍,以后有机会再继续深入!

那么接下来就开始吧,如果文章有错误的地方请大家多多包涵,不吝在评论区指正哦~

声明:本文使用JDK1.8

一、线程池简介

线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用。

我们来看看如果没有使用线程池的情况是这样的:

  • 为每个请求都新开一个线程!

public class ThreadPerTaskWebServer {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(80);
        while (true) {
            // 为每个请求都创建一个新的线程
            final Socket connection = socket.accept();
            Runnable task = () -> handleRequest(connection);
            new Thread(task).start();
        }
    }
    private static void handleRequest(Socket connection) {
        // request-handling logic here
    }
}

为每个请求都开一个新的线程虽然理论上是可以的,但是会有缺点:

  • 线程生命周期的开销非常高。每个线程都有自己的生命周期,创建和销毁线程所花费的时间和资源可能比处理客户端的任务花费的时间和资源更多,并且还会有某些空闲线程也会占用资源。
  • 程序的稳定性和健壮性会下降,每个请求开一个线程。如果受到了恶意攻击或者请求过多(内存不足),程序很容易就奔溃掉了。

所以说:我们的线程最好是交由线程池来管理,这样可以减少对线程生命周期的管理,一定程度上提高性能。

二、JDK提供的线程池API

JDK给我们提供了Excutor框架来使用线程池,它是线程池的基础。

  • Executor提供了一种将“任务提交”与“任务执行”分离开来的机制(解耦)

下面我们来看看JDK线程池的总体api架构:

接下来我们把这些API都过一遍看看:

Executor接口:

ExcutorService接口:

AbstractExecutorService类:

ScheduledExecutorService接口:

ThreadPoolExecutor类:

ScheduledThreadPoolExecutor类:

2.1ForkJoinPool线程池

除了ScheduledThreadPoolExecutor和ThreadPoolExecutor类线程池以外,还有一个是JDK1.7新增的线程池:ForkJoinPool线程池

于是我们的类图就可以变得完整一些:

JDK1.7中新增的一个线程池,与ThreadPoolExecutor一样,同样继承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的两大核心类之一。与其它类型的ExecutorService相比,其主要的不同在于采用了工作窃取算法(work-stealing):所有池中线程会尝试找到并执行已被提交到池中的或由其他线程创建的任务。这样很少有线程会处于空闲状态,非常高效。这使得能够有效地处理以下情景:大多数由任务产生大量子任务的情况;从外部客户端大量提交小任务到池中的情况。

来源:

2.2补充:Callable和Future

学到了线程池,我们可以很容易地发现:很多的API都有Callable和Future这么两个东西。


    Future<?> submit(Runnable task)
    <T> Future<T> submit(Callable<T> task)

其实它们也不是什么高深的东西~~~

我们可以简单认为:Callable就是Runnable的扩展。

  • Runnable没有返回值,不能抛出受检查的异常,而Callable可以!

也就是说:当我们的任务需要返回值的时,我们就可以使用Callable!

Future一般我们认为是Callable的返回值,但他其实代表的是任务的生命周期(当然了,它是能获取得到Callable的返回值的)

简单来看一下他们的用法:


public class CallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);

        // 可以执行Runnable对象或者Callable对象代表的线程
        Future<Integer> f1 = pool.submit(new MyCallable(100));
        Future<Integer> f2 = pool.submit(new MyCallable(200));

        // V get()
        Integer i1 = f1.get();
        Integer i2 = f2.get();

        System.out.println(i1);
        System.out.println(i2);

        // 结束
        pool.shutdown();
    }
}

Callable任务:


public class MyCallable implements Callable<Integer> {

    private int number;

    public MyCallable(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }

}

执行完任务之后可以获取得到任务返回的数据:

三、ThreadPoolExecutor详解

这是用得最多的线程池,所以本文会重点讲解它。

我们来看看顶部注释:

3.1内部状态

变量ctl定义为AtomicInteger,记录了“线程池中的任务数量”和“线程池的状态”两个信息。

线程的状态:

  • RUNNING:线程池能够接受新任务,以及对新添加的任务进行处理。
  • SHUTDOWN:线程池不可以接受新任务,但是可以对已添加的任务进行处理。
  • STOP:线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
  • TIDYING:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
  • TERMINATED:线程池彻底终止的状态。

各个状态之间转换:

3.2已默认实现的池

下面我就列举三个比较常见的实现池:

  • newFixedThreadPool
  • newCachedThreadPool
  • SingleThreadExecutor

如果读懂了上面对应的策略呀,线程数量这些,应该就不会太难看懂了。

3.2.1newFixedThreadPool

一个固定线程数的线程池,它将返回一个corePoolSize和maximumPoolSize相等的线程池。


   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

3.2.2newCachedThreadPool

非常有弹性的线程池,对于新的任务,如果此时线程池里没有空闲线程,线程池会毫不犹豫的创建一条新的线程去处理这个任务。


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

3.2.3SingleThreadExecutor

使用单个worker线程的Executor


public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.3构造方法

我们读完上面的默认实现池还有对应的属性,再回到构造方法看看

  • 构造方法可以让我们自定义(扩展)线程池

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. 指定核心线程数量
  2. 指定最大线程数量
  3. 允许线程空闲时间
  4. 时间对象
  5. 阻塞队列
  6. 线程工厂
  7. 任务拒绝策略

再总结一遍这些参数的要点:

线程数量要点:

  • 如果运行线程的数量少于核心线程数量,则创建新的线程处理请求
  • 如果运行线程的数量大于核心线程数量,小于最大线程数量,则当队列满的时候才创建新的线程
  • 如果核心线程数量等于最大线程数量,那么将创建固定大小的连接池
  • 如果设置了最大线程数量为无穷,那么允许线程池适合任意的并发数量

线程空闲时间要点:

  • 当前线程数大于核心线程数,如果空闲时间已经超过了,那该线程会销毁。

排队策略要点:

  • 同步移交:不会放到队列中,而是等待线程执行它。如果当前线程没有执行,很可能会新开一个线程执行。
  • 无界限策略:如果核心线程都在工作,该线程会放到队列中。所以线程数不会超过核心线程数
  • 有界限策略:可以避免资源耗尽,但是一定程度上减低了吞吐量

当线程关闭或者线程数量满了和队列饱和了,就有拒绝任务的情况了:

拒绝任务策略:

  • 直接抛出异常
  • 使用调用者的线程来处理
  • 直接丢掉这个任务
  • 丢掉最老的任务

四、execute执行方法

execute执行方法分了三步,以注释的方式写在代码上了~


    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果线程池中运行的线程数量<corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

        //如果线程池中运行的线程数量>=corePoolSize,且线程池处于RUNNING状态,且把提交的任务成功放入阻塞队列中,就再次检查线程池的状态,
            // 1.如果线程池不是RUNNING状态,且成功从阻塞队列中删除任务,则该任务由当前 RejectedExecutionHandler 处理。
            // 2.否则如果线程池中运行的线程数量为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。
        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);
        }
        // 如果以上两种case不成立,即没能将任务成功放入阻塞队列中,且addWoker新建线程失败,则该任务由当前 RejectedExecutionHandler 处理。
        else if (!addWorker(command, false))
            reject(command);
    }

五、线程池关闭

ThreadPoolExecutor提供了shutdown()和shutdownNow()两个方法来关闭线程池

shutdown() :

shutdownNow():

区别:

  • 调用shutdown()后,线程池状态立刻变为SHUTDOWN,而调用shutdownNow(),线程池状态立刻变为STOP。
  • shutdown()等待任务执行完才中断线程,而shutdownNow()不等任务执行完就中断了线程。

六、总结

本篇博文主要简单地将多线程的结构体系过了一篇,讲了最常用的ThreadPoolExecutor线程池是怎么使用的~~~

明天希望可以把死锁写出来,敬请期待~~~

还有剩下的几个线程池(给出了参考资料):

参考资料:

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友

文章的目录导航:

Blender学习笔记——制作向量场 #下载Blender - UE4 - FGA Vector Field Editor插件的压缩包,Github上的下载地址如下:https://github.com/isathar/Blender_UE4_VectorFieldEditor #下载并解压后将FGA VectorFields文件夹复制到C:\Program Files\Blender Foundation\Blender 2.83\2.83\scripts\addons目录下,此步骤可根据blender安装的位置自行调整,只要放入addon 阅读详情

相关推荐

关于RS485通讯TVS器件选择的经验

先说经验结论 如果你的RS485用于频繁热拔插, 比如作为手持终端使用, 且手持器与目标板非隔离, 那么使用6.8CA可能是更好的选择.因为有热拔插会产生浪涌, 而且在非隔离的场合有些工业设备接地也是无法保障的.对于使用场景较为单一的, 点对点或点对多场合. 可以使用最大峰值脉冲电流较小的SM712.这是在长期的产品设计中得到的经验. 仅供参考 一. TVS的工作原理和作用 瞬态电压抑制二极管 ...

qq_20163075的博客 6510

Java有序线程池,设计具有相同属性任务串行有序执行的线程池

简单想想可以这样做:创建一个自定义的等待队列,线程使用PooledExecutor即可:1、所有任务通过等待队列添加,而不是通过线程池的方式添加(线程池有时候不会放到等待队列里面,造成不一致的问题;2、将用户作为一个任务,一个任务包含了用户的多个子任务;3、添加任务,即队列入队操作:查找用户是否已经存在,如果存在则加到存在的用户任务列表的末尾;4、取出任务执行:按照策略的方式(这个可能比较重要,最...

weixin_39966020的博客 598

QMT量化交易桥接方案深度对比:从MiniQMT淘汰说起

量化交易系统中的API桥接,本质是解决本地终端与底层交易引擎之间的通信瓶颈。其核心原理在于绕过GUI封装、直连确定性执行层,从而获得低延迟、高可靠、可监控的指令通路。技术价值体现在策略实盘一致性提升、毫秒级响应保障及运维可控性增强。典型应用场景包括打板策略、T+0套利、高频网格等对时效与稳定性双敏感的自动化交易。本文聚焦国金证券QMT专业版生态,系统剖析DLL注入、HTTP反向代理、IPC共享内存与官方HTTP API四大桥接路径的性能边界、合规风险与工程落地成本,尤其深入解析qmt终端 client is

weixin_33892359的博客 457

java如何让线程乱序执行_多线程如何按指定顺序同步执行

笔者今天看到一个有趣的面试题,如何让多个线程按照既定的顺序依次执行?比如每个线程输出一个整数,那么期望就是这样的:0,1,2,3,4,5,6,7,8,9.而不是0,2,4,1,3,5,8,7,9,6乍一看,这不是反人性的考题吗?多线程本来就以乱序执行出名的。稍加思索,想到3种解决方案,分别用代码实现之。方法1 使用newSingleThreadExecutornewSingleThreadExec...

weixin_30109877的博客 833

攀藤PMS系列传感器STM32驱动开发与数据解析实战

本文详细介绍了攀藤PMS系列传感器与STM32微控制器的驱动开发与数据解析实战,涵盖硬件连接、数据协议解析、驱动源码编写及数据处理优化。重点解析了传感器数据帧结构,提供了完整的STM32驱动实现代码,并分享了实际应用中的问题排查与性能优化经验,帮助开发者快速实现高精度颗粒物浓度检测。

milk8的博客 348

SimpleFOC STM32实战:基于F103与HAL库,剖析速度环与电流环的协同控制

本文详细介绍了基于STM32F103与HAL库的SimpleFOC实战应用,重点剖析速度闭环控制与电流环的协同工作原理。通过开发环境搭建、参数调试技巧及性能优化方案,帮助开发者高效实现无刷电机精准控制,适用于机器人、无人机等高精度应用场景。

weixin_29170327的博客 476

多线程 线程池优缺点

● 多线程 多线程的概念很好理解就是多条线程同时存在,但要用好多线程确不容易,涉及到多线程间通信,多线程共用一个资源等诸多问题。 使用多线程的优缺点: 优点: 1)适当的提高程序的执行效率(多个线程同时执行)。 2)适当的提高了资源利用率(CPU、内存等)。 缺点: 1)占用一定的内存空间。 2)线程越多CPU的调度开销越大。 3)程序的复杂度会上升。 线程池的优点 1)降...

qq_37924084的博客 5032

基于Simulink的电机驱动逆变器仿真

本文完成了电机驱动逆变器的Simulink多层级仿真平台搭建,实现了:✅ 构建了从理想到器件级的逆变器模型体系✅ 实现了Si/SiC损耗精确计算与结温预测✅ 建模了母线寄生参数、死区效应、栅极驱动等关键非理想特性✅ 支持故障注入与保护策略验证✅ 集成了热管理、EMI、寿命预测与碳足迹评估。

amy_mhd的博客 87

Java线程池--为什么使用线程池?优缺点是什么?

本文介绍为什么使用线程池以及它的优缺点是什么。

IT利刃出鞘的博客 8806

在SAP系统中创建物料 860000000000000082(物料描述为“8680月球广寒宫坦克车”)时,系统报出了一个错误

摘要:SAP系统创建物料"8680月球广寒宫坦克车"时出现"C+020"错误,提示评估区域C916未在物料分类账中设置为生产性。解决方案需进入"会计-控制-产品成本控制-实际成本核算"路径,将评估区域C916手动设置为生产性状态。该操作需由具备权限的系统管理员或FICO顾问执行,建议操作前进行数据备份或在测试环境验证。完成设置后即可重新创建物料。

金牌架构师 391

整车的智能配电系统

本文探讨了汽车电网安全的重要性,分析了传统汽车电网的现状,并重点介绍了半导体在汽车智能配电系统中的作用。半导体凭借其单向导电性和可控性,如二极管、MOSFET等,提供了更高效、安全的电力控制。通过对比传统保险丝与半导体器件,阐述了半导体在过流保护、电压监测和故障诊断方面的优势,强调其在减少安全隐患、提高能源管理和续航里程方面的贡献。极氪智能配电系统展示了如何运用半导体技术实现整车能源的精细化管理,降低维修成本,减少线束复杂性,提升安全性能。

让你爱上电路设计 1612

线程池初步了解

做mapview的时候涉及到线程池的相关知识,今天学习一下,并做记录。version1.0 2023年6月14日14:35:22提示:以下是本篇文章正文内容,下面案例可供参考提示:这里对文章进行总结:例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

晴山の博客 204

【Claude】SSL certificate verification 错误:证书校验失败与自定义 CA 配置

获取并验证企业 CA 证书后,需要配置 Claude Code(Node.js)信任该证书。Claude Code 的 SSL 错误通常源于企业代理:不是 Anthropic 服务器的问题Node.js 使用独立的 CA 存储:操作系统信任了企业 CA 不等于 Node.js 也信任NODE_EXTRA_CA_CERTS 是标准解决方案:指向包含企业 CA 的 PEM 文件绝不使用 NODE_TLS_REJECT_UNAUTHORIZED=0:这等于关闭所有 HTTPS 安全验证证书链完整性很重要。

博客 6808

探索JAVA并发 - 线程池详解

作者:acupt,80后资深Java工程师一枚!架构师社区合伙人!线程池是并发编程中必不可少的一种工具,也是面试高频话题。线程池,即管理着若干线程的资源池(字面意思)。相...

架构师小秘圈 306

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

线程池参数详解_探索JAVA并发 - 线程池详解

线程池是并发编程中必不可少的一种工具,也是面试高频话题。线程池,即管理着若干线程的资源池(字面意思)。相比于为每个任务分配一个线程,在线程池中执行任务优势更多:1.线程复用:线程池中的线程是可以复用的,省去了创建、销毁线程的开销,提高了资源利用率(创建、销毁等操作都是要消耗系统资源的)和响应速度(任务提交过来线程已存在就不用等待线程创建了);2.合理利用资源:通过调整线程池大小,让所有处理器尽量保...

weixin_39997311的博客 106

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

四大线程池详解

new Thread 的弊端首先看一段代码:/** * Created by Zero on 2017/8/30. */ public class ThreadTest { public static void main(String[] args) { while (true) { new Thread(new Runnable() {

Zero 3万+

线程池相关问题

线程池怎么动态调整参数 被问到的时候我也被整蒙了。我知道线程池大概怎么使用,但是因为项目没使用到过,所以还真不知道怎么动态调整。唯一知道的是核心线程数与最大线程数的区别是核心线程数去工作队列取任务不会超时,所以线程一直存在,而多出来的线程数取任务会存在超时的情况,自然而然就结束了。当时因为不确定是否有此api可以运行时改变参数,所以不敢贸然回答。 ThreadPoolExecutor的几个参数就不去解释了。 我自己百度并且尝试了一下,发现衍生的4种线程池都没有相关的api设置参数,只有threadp

qq_37822914的博客 420

多线程学习(八)---线程池

参考文章: 线程池你真不来了解一下吗? 一、 线程池简介 线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用。 如果没有使用线程池,为每个请求都新开一个线程: public class Test { public static void main(Strin...

一个胖子IT男的博客 496

为什么线程池里的方法会执行两次_面试官问你java都有哪些线程池,自己是否自定义过线程池...

我还记得大学实习面试时,被问到什么是线程池这个问题,因为这个题我被录取了,原因就是我背出来了,而另外一个面试的没背出来,说实话当时还真不知道它是干什么的,就是看面试题给背下来了,在之后就是在实际开发中,确实使用到了。首先说什么是线程池,为什么使用线程池?线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都...

weixin_39987847的博客 133

Java生产环境线程池使用场景

talk is cheap, show me the code 直接上代码 import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; /** * @author by bixi.lx * @created on 2018 07 28 22:17 */ public class Th...

csucoderlee的专栏 8258
上一篇: Object对象你真理解了吗?
下一篇: 多线程之死锁就是这么简单
Java3y
Java3y 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 1万+粉丝 509原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值