Java线程池工作原理浅析

为什么要用线程池?

1、线程属于稀缺资源,它的创建会消耗大量系统资源

2、线程频繁地销毁,会频繁地触发GC机制,使系统性能降低

3、多线程并发执行缺乏统一的管理与监控

线程池的使用

线程池的创建使用可通过Executors类来完成,它提供了创建线程池的常用方法。

·newFixedThreadPool

·newSingleThreadExecutor

·newCachedThreadPool

·newScheduledThreadPool

用例

public static void main(String[] args) {
        test();
    }

    public static void test() {
        ExecutorService service = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 30; i++) {
//            System.out.println("task name:addition_isCorrect");
            service.execute(new MyRunnable("executor-" + i));
        }

    }

    public static class MyRunnable implements Runnable {

        String name;

        public MyRunnable(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                System.out.println( );
                System.out.println("time = " + System.currentTimeMillis() + "--task name:" + name + "---thread name:" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
time = 1711718334177--task name:executor-1---thread name:pool-1-thread-2

time = 1711718334177--task name:executor-0---thread name:pool-1-thread-1
time = 1711718334177--task name:executor-2---thread name:pool-1-thread-3



time = 1711718337191--task name:executor-5---thread name:pool-1-thread-3
time = 1711718337191--task name:executor-4---thread name:pool-1-thread-1
time = 1711718337191--task name:executor-3---thread name:pool-1-thread-2


time = 1711718340193--task name:executor-6---thread name:pool-1-thread-3
time = 1711718340193--task name:executor-7---thread name:pool-1-thread-1

time = 1711718340201--task name:executor-8---thread name:pool-1-thread-2

time = 1711718343204--task name:executor-10---thread name:pool-1-thread-1


time = 1711718343204--task name:executor-9---thread name:pool-1-thread-3
time = 1711718343204--task name:executor-11---thread name:pool-1-thread-2


time = 1711718346216--task name:executor-12---thread name:pool-1-thread-1

time = 1711718346216--task name:executor-14---thread name:pool-1-thread-2
time = 1711718346216--task name:executor-13---thread name:pool-1-thread-3

time = 1711718349226--task name:executor-17---thread name:pool-1-thread-3


time = 1711718349226--task name:executor-15---thread name:pool-1-thread-1
time = 1711718349226--task name:executor-16---thread name:pool-1-thread-2



time = 1711718352241--task name:executor-19---thread name:pool-1-thread-1
time = 1711718352241--task name:executor-20---thread name:pool-1-thread-2
time = 1711718352241--task name:executor-18---thread name:pool-1-thread-3


time = 1711718355242--task name:executor-21---thread name:pool-1-thread-1
time = 1711718355242--task name:executor-22---thread name:pool-1-thread-2

time = 1711718355254--task name:executor-23---thread name:pool-1-thread-3


time = 1711718358245--task name:executor-24---thread name:pool-1-thread-1
time = 1711718358245--task name:executor-25---thread name:pool-1-thread-2

time = 1711718358261--task name:executor-26---thread name:pool-1-thread-3


time = 1711718361250--task name:executor-27---thread name:pool-1-thread-1
time = 1711718361250--task name:executor-28---thread name:pool-1-thread-2

time = 1711718361266--task name:executor-29---thread name:pool-1-thread-3

通过打印出来的日志可以看到,所有任务都在名为name:pool-1-thread-1、pool-1-thread-2、pool-1-thread-3的线程中运行,这与我们设置的线程池大小相符。在这个线程池中,前面三个任务优先执行,后面的任务都在等待。

如果把ExecutorService service = Executors.newFixedThreadPool(3);改为ExecutorService service = Executors.newCachedThreadPool();

time = 1711718860800--task name:executor-0---thread name:pool-1-thread-1
time = 1711718860823--task name:executor-23---thread name:pool-1-thread-24
time = 1711718860825--task name:executor-2---thread name:pool-1-thread-3
time = 1711718860826--task name:executor-25---thread name:pool-1-thread-26
time = 1711718860829--task name:executor-8---thread name:pool-1-thread-9
time = 1711718860825--task name:executor-26---thread name:pool-1-thread-27
time = 1711718860825--task name:executor-7---thread name:pool-1-thread-8
time = 1711718860825--task name:executor-20---thread name:pool-1-thread-21
time = 1711718860825--task name:executor-13---thread name:pool-1-thread-14
time = 1711718860823--task name:executor-22---thread name:pool-1-thread-23
time = 1711718860828--task name:executor-1---thread name:pool-1-thread-2
time = 1711718860828--task name:executor-14---thread name:pool-1-thread-15
time = 1711718860828--task name:executor-19---thread name:pool-1-thread-20
time = 1711718860828--task name:executor-29---thread name:pool-1-thread-30
time = 1711718860828--task name:executor-24---thread name:pool-1-thread-25
time = 1711718860828--task name:executor-5---thread name:pool-1-thread-6
time = 1711718860828--task name:executor-6---thread name:pool-1-thread-7
time = 1711718860828--task name:executor-4---thread name:pool-1-thread-5
time = 1711718860827--task name:executor-10---thread name:pool-1-thread-11
time = 1711718860827--task name:executor-9---thread name:pool-1-thread-10
time = 1711718860827--task name:executor-3---thread name:pool-1-thread-4
time = 1711718860827--task name:executor-12---thread name:pool-1-thread-13
time = 1711718860827--task name:executor-15---thread name:pool-1-thread-16
time = 1711718860827--task name:executor-18---thread name:pool-1-thread-19
time = 1711718860827--task name:executor-27---thread name:pool-1-thread-28
time = 1711718860826--task name:executor-17---thread name:pool-1-thread-18
time = 1711718860826--task name:executor-16---thread name:pool-1-thread-17
time = 1711718860826--task name:executor-11---thread name:pool-1-thread-12
time = 1711718860826--task name:executor-21---thread name:pool-1-thread-22
time = 1711718860826--task name:executor-28---thread name:pool-1-thread-29

可以看到,一瞬间任务就都执行完了,可以想象出,newCachedThread()方式创建的线程池,执行任务时会创建足够多的线程。

 下面我们看一下它们是如何被创建出来的

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

..........构造函数..........
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;
}

构造方法参数说明:

corePoolSize:核心线程数,除非设置核心线程超时-allowCoreThreadTimeOut,否则线程会一直存活在线程池中,即使线程池处于空闲状态。

maximumPoolSize:线程池中允许存在的最大线程数。

workQueue:工作队列,当核心线程都处于繁忙状态时,将任务提交到工作队列中。如果工作队列也超过了容量,会去尝试创建一个非核心线程执行任务。

keepAliveTime:非核心线程处理空闲状态最长时间,超过该值线程将会被回收。

threadFactory:线程工厂类,用于创建线程。

RejectedExecutionHandler:工作队列饱和策略,比如丢弃,抛出异常等。

线程池创建完成后,可通过execute方法提交任务,线程池根据当期运行状态和特定参数对任务进行处理。

线程池执行流程

线程池类型核心线程数最大线程数非核心线程空闲时间工作队列
newFixedThreadPoolspecificspecific0LinkedBlockingQueue
newSingleThreadExecutor110LinkedBlockingQueue
newCachedThreadPool0Integer.MAX_VALUE60SSynchronousQueue
newScheduledThreadPoolspecificINteger.MAX_VALUE0DelayedWorkQueue

specific表示使用者传入的固定值。

阻塞队列

为什么要用阻塞队列?

阻塞队列常用于生产者-消费者模型,任务的添加是生产者,任务的调度执行是消费者,他们通常在不同的线程中,如果使用非阻塞队列,那么就需要使用额外的处理同步策略和线程间唤醒策略。比如当前任务队列为空时,消费者线程取元素会被阻塞,当有新的任务添加到队列中时,需要唤醒消费者线程处理任务。

阻塞队列的实现就是在添加元素和获取元素时设置了各种锁操作。

同时,非核心线程是根据阻塞队列的容量进行创建的。具体点就是当阻塞队列未满时,并不会创建非核心线程,而是将任务继续添加到阻塞队列后面等待核心线程执行。

LinkedBlockingQueue:内部使用链表实现的阻塞队列,默认构造函数使用Integer.MAX_VALUE作为容量,另外可通过带capacity参数的构造函数限制容量,使用Executors工具类创建的线程池容量均为Integer.MAX.

SynchronousQueue:容量为0,每当有任务添加进来时会立即触发消费,即每次插入操作一定伴随一个移除操作,反之亦然。

DelayedWorkQueue:用数组实现的,默认容量是16,支持动态扩容,可对延迟任务进行排序,类似优先级队列,搭配ScheduledThreadPoolExecutor可定时或延迟任务。

ArrayBlockingQueue:它不在上述线程池体系中,是基于数组实现,容量固定且不可扩容。

使用场景:

newFixedThreadPool:它的特点是没有非核心线程,这意味着即使任务过多也不会创建新的线程,即使任务闲置也仍然保留一定数量的核心线程,等待队列无线,性能相对稳定,适用于长期有任务要执行,同时任务量也不大的场景。

newSingleThreadExecutor:相当于线程数量为1的newFixedThreadPool,因为线程数量为1,所以适用于任务需要顺序执行的场景。

newCachedThreadPool:它的特点是没有核心线程,非核心线程无线,可短时间内处理无限多的任务,但实际上创建线程十分消耗资源,过多的创建线程可能导致oom,同时该线程池还设置了超时时间,还涉及到线程资源的释放,大量任务并行时性能不稳定,少量任务并行且后续不再执行其他任务的场景可用。

newScheduledThreadPool:通常用于定时或延迟任务。

实际项目开发过程中,不建议直接使用Executors提供的发放创建线程池,如果任务规模,响应时间大致确定,应根据实际需求通过ThreadPoolExecutor各种构造函数手动创建,自由控制线程数、超时时间、阻塞队列、饱和策略等内容(默认饱和策略都是AbortPolicy即抛出异常)。

饱和策略

DiscardPolicy:将丢弃被拒绝的任务

DiscardOldestPolicy:将丢弃队列头部的任务,即先入队的任务会出队以腾出空间

AbortPolicy:抛出RejectedExecutionException异常

CallerRunsPolicy:在execute方法的调用线程中运行被拒绝的任务。

用户也可以通过实现RejectedExecutionHandler接口自定义饱和策略,并通过ThreadPoolExecutor的构造函数传入。

线程池的继承结构

Executor:基类接口,仅定义一个execute方法。

ExecutorService:继承了Executor的接口,定义了带返回值的任务提交方法submit,以及关闭线程池的shutdown方法

AbstractExecutorService:实现了ExecutorService的大部分接口,剩余shutdown与execute方法为实现

ThreadPoolExecutor:常用线程池类

ScheduledThreadPoolExecutor:定义了一系列支持延迟执行任务的线程池

ForkJoinPool:它采用分治思想,将一个任务细分为多个子任务在多线程中执行。

线程池大小选定

需要了解任务是CPU密集型还是IO密集型

CPU密集型:比如大量的计算任务,CPU占用率高,那么此时如果多开线程反而会因为CPU频繁做线程调度导致性能降低。一般建议线程数为CPU核心数+1,+1是为了防止某个核心线程阻塞或意外中断时作为候补。

IO密集型:通常指文件IO、网络IO等。线程数的选取与IO耗时和CPU耗时的比例有关,最佳线程数=CPU核数*[1+(IO耗时/CPU耗时)],之所以设置比例是为了使IO设备和CPU的利用率都达到最高。

线程池的状态

线程池的状态在整个任务处理过程中至关重要,比如添加任务时会先判断线程池是否处于运行状态,任务添加到队列后再判断运行状态,如果此时线程池已经关闭则移除任务并执行饱和策略。

RUNNING:能接收新任务,并且也能处理阻塞队列中的任务

SHUTDOWN:关闭状态,不能接受新任务,但可以处理阻塞队列中已保存的任务。

STOP:不能接受新任务,也不能处理队列中的人物,会中断正在处理任务的线程。

TIDYING:如果所有任务已经终止,workerCount为0,线程池进入该状态后会调用terminated方法进入TERMINATED状态

TERMINATED:在terminated方法执行完之后进入该状态,默认terminated方法中什么也没有做。

参考:Java线程池工作原理浅析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/502678.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【网站项目】泉文化管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Vue2(十二):Vuex环境搭建、Vuex工作原理、几个配置项、多组件共享数据、Vuex模块化

一、Vuex 1.概念 专门在Vue中实现集中式状态&#xff08;数据&#xff09;管理的一个Vue插件&#xff08;use引入&#xff09;&#xff0c;对vue应用中多个组件的共享状态进行集中式的管理&#xff08;读&#xff0f;写&#xff09;&#xff0c;也是一种组件间通信的方式&…

String,StringBuffer,StringBuilder 的区别【大白话Java面试题】

String&#xff0c;StringBuffer&#xff0c;StringBuilder 的区别【大白话Java面试题】 大白话回答 1、可变/不可变类 String是不可变类。他被被final修饰&#xff0c;所以每一次的创建修改删除都要重新分配内存创建新的对象。 StringBuilder和StringBuffer是可变类&#xff…

Linux部署Sonarqube+Gogs+Jenkins(一)

Linux部署SonarqubeGogsJenkins 一、1.Linux安装JDK11环境1. 本地进行上传2. 进入到/usr/java目录&#xff0c;并且进行解压3. 配置文件/etc/profile&#xff0c;配置环境变量4.让对应的配置文件生效5. 验证 二、Linux安装Python环境三、Linux安装Jenkins环境1、/usr目录下创建…

ssm框架笔记-maven

html是骨头 css使皮肤 js是你能做的动作 MAVEN 依赖管理&#xff1a;1.声明dependenciys标签 2.maven search3。 版本号提取 3.$引用 3.2依赖传递和冲突 依赖传递指的是当一个模块或库 A 依赖于另一个模块或库 B&#xff0c;而 B 又依赖于模块或库 C&#xff0c;那么 A 会间…

ADT 创建表,并用ABAP往里面插数据

参考&#xff1a;Create Table Persistence and Generate Data | SAP Tutorials 4、Replace your code with following: CLASS zcl_generate_travel_data_xxx DEFINITIONPUBLICFINALCREATE PUBLIC .PUBLIC SECTION.INTERFACES if_oo_adt_classrun.PROTECTED SECTION.PRIVATE S…

基于SSM医院病历管理系统

基于SSM医院病历管理系统的设计与实现 摘要 病历管理系统是医院管理系统的重要组成,在计算机技术快速发展之前&#xff0c;病人或者医生如果想记录并查看自己的健康信息是非常麻烦的&#xff0c;因为在以往病人的健康信息通常只保存在自己的病历卡或者就诊报告中&#xff0c;…

【C++】vector的介绍及使用说明(类模版的实现方式,顺序存储与动态数组,迭代器iterator的运用,vector的增删查改)

目录 00.引言 01.vector的介绍 类模版 动态分配内存 顺序存储 02.vector的使用 构造函数 迭代器iterator 1.分类&#xff1a; 2.运用&#xff1a; 扩容 1.resize() 2.reverse() 增删查改 1.增加 2.删除 3.查找 4.修改 00.引言 以前我们讲过string类&#xff0…

如何系统的自学python?

系统地自学Python是一个循序渐进的过程&#xff0c;以下是一份详细的指南&#xff0c;帮助你从零开始逐步掌握这门语言&#xff1a; 1、了解Python及其应用场景&#xff1a; 阅读关于Python的简介&#xff0c;理解它为何流行&#xff0c;以及在哪些领域&#xff08;如Web开发…

stream流中的坑,peek/map/filter

起因 所在系统为一个对账系统&#xff0c;涉及的业务为发布账单&#xff0c;数据结构定的是供应商账单发布&#xff0c;生成企业账单和个人账单。发布账单处理完本系统业务后&#xff0c;需要生成站内通知和调用外部接口生成短信通知。后来增加需求&#xff0c;需要在发布完成…

【Qt 学习笔记】Day1 | Qt 开发环境的搭建

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Day1 | Qt 开发环境的搭建 文章编号&#xff1a;Qt 学习笔记 / 02 文…

C++初学者:优雅创建第一个窗口

我想学习C做一些实用的程序&#xff0c;但是我不想在软件界面上花太多的时间&#xff0c;可是每每就是界面影响我的思绪。 今天学习C类的包装知识&#xff0c;终于整出了一个我的界面类&#xff0c;虽然封装水平很弱&#xff0c; 这次就用这个类&#xff0c;写了自己工作上常用…

Node.js中Router的使用

文章目录 介绍router的优点1.导入Express和创建Router&#xff1a;2. 定义路由&#xff1a;3.将router暴露到模块外&#xff1a;4. 将Router挂载到Express应用中&#xff1a;4.1.引入router4.2.使用中间件让router在Express应用中生效(三种写法) 5. 完整示例&#xff1a;5.1.编…

Vue3+Vite Nginx部署 跨域

打包项目 webstorm打开项目之后&#xff0c;在Terminal执行打包命令 pnpm run build:prod 复制到Nginx 打包完成之后,生成的包在根目录dist&#xff0c;把dist目录拷贝到Nginx放网站目录下&#xff1a;\nginx-1.25.2\html\divided &#xff0c;dist改名了divided 修改配置…

【JavaSE】内部类

目录 前言 内部类 内部类的种类 1. 实例内部类 2 静态内部类 3 匿名内部类 4 局部内部类 结语 前言 内部类是我们前面学习遗留下来的知识点&#xff0c;在学完接口后才能更好的理解它&#xff0c;因此等到现在才讲 内部类 在Java中&#xff0c;我们可以将A类定义在B…

短视频素材哪里去找?五大网站助你轻松解决素材难题!

你好&#xff0c;短视频小能手们&#xff0c;是不是经常在为找不到好看的视频素材而烦恼&#xff1f;不用怕&#xff0c;今天我要为你们揭秘五个超赞的视频素材网站&#xff0c;让你的视频素材&#xff0c;制作事半功倍&#xff0c;轻松赢得点赞和关注&#xff01;瞬间成为热门…

关于Windows中AppData的相关知识,看这篇文章就可以了

如果AppData文件夹占用了你电脑上的太多空间,则需要清理AppData文件夹。下面是一些帮助你在Windows计算机上进行AppData清理的方法。 什么是AppData文件夹 AppData文件夹是保存应用程序数据和设置的位置。每个Windows计算机在C驱动器上都有一个AppData文件夹。AppData文件夹…

自己动手用ESP32手搓一个智能机器人:ESP32-CAM AI Robot

目录 介绍 硬件需求 软件需求 步骤 总结 源码下载 介绍 ESP32-CAM是一款集成了Wi-Fi和蓝牙功能的微控制器模块&#xff0c;同时还集成了摄像头接口&#xff0c;使其成为一个非常适合构建智能机器人的选择。在本项目中&#xff0c;我将向您展示如何使用ESP32-CAM模块构建…

C# winform校验文件版本差异及版本号

界面 代码 using System.Diagnostics;namespace VersionTool {public partial class Form1 : Form{List<string> fileNmaes new List<string>() { "PhotoMes.Base.dll", "PhotoMes.App.exe", "PhotoMes.Cameras.dll" };public F…

JavaScript高级 —— 学习(二)

一、深入对象 &#xff08;一&#xff09;创建对象三种方式 1.利用对象字面量创建 <body><script>const obj {}</script> </body> 2.利用 new Object() 创建 <body><script>const obj new Object({uname: 一个人})console.log(obj)…