关于如何用好线程池的一些建议

news2024/11/25 10:45:57

文章目录

    • 1. 线程的使用场景
    • 2. 线程池创建
    • 3. 参数的配置建议
      • 常见的拒绝策略
      • 其他的拒绝策略
    • 4. 线程池的任务处理流程
    • 5. 线程的状态
    • 6. 线程池的监控

1. 线程的使用场景

异步任务

简单来说就是某些不需要同步返回业务处理结果的场景,比如:短信、邮件等通知类业务,评论、点赞等互动性业务。

并行计算

就像MapReduce一样,充分利用多线程的并行计算能力,将大任务拆分为多个子任务,最后再将所有子任务计算后的结果进行汇总,ForkJoinPool就是JDK中典型的并行计算框架。

串行任务

很简单,假设某个方法需要经过,A、B、C三个步骤,A步骤耗时1秒,B步骤耗时2秒,C步骤耗时3秒,那么如果是串行处理,则该方法最终需要耗时6秒,但如果A、B、C三个步骤互相之间是没有依赖的,那么就可以利用多线程的方式,同时处理三个步骤,这样该方法只需要等待耗时最长的步骤结束即可。

2. 线程池创建

不要直接使用Executors创建线程池,应通过ThreadPoolExecutor的方式,主动明确线程池的参数,避免产生意外。

每个参数都要显示设置,例如像下面这样:

private static final ExecutorService executor = new ThreadPoolExecutor(
        2,
        4,
        1L,
        TimeUnit.MINUTES,
        new LinkedBlockingQueue<>(100),
        new ThreadFactoryBuilder().setNameFormat("common-pool-%d").build(),
        new ThreadPoolExecutor.CallerRunsPolicy());

3. 参数的配置建议

CorePoolSize(核心线程数)

一般在配置核心线程数的时候,是需要结合线程池将要处理任务的特性来决定的,而任务的性质一般可以划分为:CPU密集型、I/O密集型。

比较通用的配置方式如下

CPU密集型:一般建议线程的核心数与CPU核心数保持一致。
I/O密集型:一般可以设置2倍的CPU核心数的线程数,因为此类任务CPU比较空闲,可以多分配点线程充分利用CPU资源来提高效率。

通过Runtime.getRuntime().availableProcessors()可以获取核心线程数。

另外还有一个公式可以借鉴

线程核心数 = cpu核心数 / (1-阻塞系数)

阻塞系数 = 阻塞时间/(阻塞时间+使用CPU的时间)

实际上大多数线上业务所消耗的时间主要就是I/O等待,因此一般线程数都可以设置的多一点,比如tomcat中默认的线程数就是200,所以最佳的核心线程数是需要根据特定场景,然后通过实际上线上允许结果分析后,再不断的进行调整。

MaximumPoolSize

maximumPoolSize的设置也是看实际应用场景,如果设置的和corePoolSize一样,那就完全依靠阻塞队列和拒绝策略来控制任务的处理情况,如果设置的比corePoolSize稍微大一点,那可能对于一些突然流量的场景更使用。

KeepAliveTime

由maximumPoolSize创建出来的线程,在经过keepAliveTime时间后进行销毁,依旧突发流量持续的时间来决定。

WorkQueue

那么阻塞队列应该设置多大呢?我们知道当线程池中所有的线程都在工作时,如果再有任务进来,就会被放到阻塞队列中等待,如果阻塞队列设置的太小,可能很快队列就满了,导致任务被丢弃或者异常(由拒绝策略决定),如果队列设置的太大,又可能会带来内存资源的紧张,甚至OOM,以及任务延迟时间过长。

所以阻塞队列的大小,又是要结合实际场景来设置的。

一般会根据处理任务的速度与任务产生的速度进行计算得到一个大概的数值。

假设现在有1个线程,每秒钟可以处理10个任务,正常情况下每秒钟产生的任务数小于10,那么此时队列长度为10就足以。
但是如果高峰时期,每秒产生的任务数会达到20,会持续10秒,且任务又不希望丢弃,那么此时队列的长度就需要设置到100。

监控workQueue中等待任务的数量是非常重要的,只有了解实际的情况,才能做出正确的决定。

ThreadFactory

通过threadFactory我们可以自定义线程组的名字,设置合理的名称将有利于你线上进行问题排查。

Handler

最后拒绝策略,这也是要结合实际的业务场景来决定采用什么样的拒绝方式,例如像过程类的数据,可以直接采用DiscardOldestPolicy策略。

常见的拒绝策略

AbortPolicy

JDK自带线程池中默认的拒绝策略,直接拒绝任务并抛出异常。

CallerRunsPolicy

由当前调用调用者继续执行当前任务。

DiscardPolicy

直接丢弃当前任务

DiscardOldestPolicy

丢弃阻塞队列中最早丢进去的任务

其他的拒绝策略

NewThreadRunsPolicy

这是Netty中的拒绝策略,和CallerRunsPolicy有点像,任务不会丢弃,不同的是Netty中是新建了一个线程继续执行当前任务。

AbortPolicyWithReport

dubbo中的拒绝策略,也是抛出异常,不同的时对于日志内容的输出更加丰富,也是为了我们更好的排查问题。

EsAbortPolicy

针对某种特定场景时,做出不同的处理方式,比如在elasticsearch中只有当isForceExecution为true(isForceExecution是用来判定任务是执行还是拒绝的条件),且阻塞队列是SizeBlockingQueue类型时,才会放入当前队列中,否则抛出异常。

4. 线程池的任务处理流程

当有请求任务到来时
池中的线程数是否小于corePoolSize
创建一个新的线程来执行任务
阻塞队列是否满了
丢入阻塞队列中,等待线程来执行
池中的线程数是否小于maximumPoolSize
执行拒绝策略
结束

阻塞队列的设计起到了良好的缓冲作用,当面对突发流量到来时,先将任务丢到队列中,再慢慢来消费,其原理和MQ是类似的,一旦队列也被打满了,则说明消费能力与你的期望对比,已经严重不足了,此时maximumPoolSize参数的设计,又给了你一次处理的机会,你可以选择再开启一部分线程来应对突发状况,当危机接触后,再主动帮你回收这部分线程,或者选择使用拒绝策略。

一个简单的任务处理,考虑各种实际运行中可能遇到的情况,对于线程池的使用者来说,也应了解线程池的任务处理流程,再结合自身的业务场景充分考虑其中的参数设置。

5. 线程的状态

Java中对线程的定义有如下几种状态:RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, NEW, TERMINATED

RUNNABLE

可运行的状态,包含了运行中和准备就绪两种状态,也就是说RUNNABLE状态下线程并不一定已经运行了,可能还在等待CPU资源。

BLOCKED

处于阻塞状态下的线程,并且这个阻塞是因为进入了同步代码块或者方法,需要等待锁的释放。

一旦线上出现blocked状态的线程,是需要排查原因的。

WAITING

处于等待状态下的线程,例如一个线程调用了Object.wait()方法,那么这个线程就会等待另一个线程调用Object.notify()或者Object.notifyAll()。
或者调用thread.join()的线程等待指定线程的终止。

常见的方法有:Object.wait()、Thread.join()、LockSupport.park()

TIMED_WAITING

与WAITING的区别就在于TIMED_WAITING是明确带有具体等待时间的,常见的方法有:Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos(long)、LockSupport.parkUntil(long)

NEW

创建了一个线程,但还没调用start()方法。

TERMINATED

终止状态,表示线程已经执行完毕。

image.png

6. 线程池的监控

线程池自身提供的统计数据

public class ThreadPoolMonitor {

    private final static Logger log = LoggerFactory.getLogger(ThreadPoolMonitor.class);

    private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 0,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
            new ThreadFactoryBuilder().setNameFormat("my_thread_pool_%d").build());

    public static void main(String[] args) {
        log.info("Pool Size: " + threadPool.getPoolSize());
        log.info("Active Thread Count: " + threadPool.getActiveCount());
        log.info("Task Queue Size: " + threadPool.getQueue().size());
        log.info("Completed Task Count: " + threadPool.getCompletedTaskCount());
    }
}

通过micrometer API完成统计,这样就可以接入Prometheus了

package com.springboot.micrometer.monitor;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.micrometer.core.instrument.Metrics;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;

@Component
public class ThreadPoolMonitor {

    private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 8, 0,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
            new ThreadFactoryBuilder().setNameFormat("my_thread_pool_%d").build(), new ThreadPoolExecutor.DiscardOldestPolicy());

    /**
     * 活跃线程数
     */
    private AtomicLong activeThreadCount = new AtomicLong(0);

    /**
     * 队列任务数
     */
    private AtomicLong taskQueueSize = new AtomicLong(0);

    /**
     * 完成任务数
     */
    private AtomicLong completedTaskCount = new AtomicLong(0);

    /**
     * 线程池中当前线程的数量
     */
    private AtomicLong poolSize = new AtomicLong(0);

    @PostConstruct
    private void init() {

        /**
         * 通过micrometer API完成统计
         *
         * gauge最典型的使用场景就是统计:list、Map、线程池、连接池等集合类型的数据
         */
        Metrics.gauge("my_thread_pool_active_thread_count", activeThreadCount);
        Metrics.gauge("my_thread_pool_task_queue_size", taskQueueSize);
        Metrics.gauge("my_thread_pool_completed_task_count", completedTaskCount);
        Metrics.gauge("my_thread_pool_size", poolSize);

        // 模拟线程池的使用
        new Thread(this::runTask).start();
    }

    private void runTask() {
        // 每5秒监控一次线程池的使用情况
        monitorThreadPoolState();
        // 模拟任务执行
        IntStream.rangeClosed(0, 500).forEach(i -> {
            // 每500毫秒,执行一个任务
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 每个处理一个任务耗时5秒
            threadPool.submit(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        });
    }

    private void monitorThreadPoolState() {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            activeThreadCount.set(threadPool.getActiveCount());
            taskQueueSize.set(threadPool.getQueue().size());
            poolSize.set(threadPool.getPoolSize());
            completedTaskCount.set(threadPool.getCompletedTaskCount());
        }, 0, 5, TimeUnit.SECONDS);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一款IP渗透小工具

MoreFind 一款用于快速导出URL、Domain和IP的小工具 快速安装 方式一: 通过Go包管理安装 go install github.com/mstxq17/MoreFindlatest 方式二: 直接安装二进制文件 wget --no-check-certificate https://ghproxy.com/https://github.com/mstxq17/MoreFind/releases/…

软件测评师2012年下半年考试真题

基础知识&#xff1a; 解析&#xff1a;死锁就是运行不下去了&#xff0c;但是这里它说资源是同类型的&#xff0c;也就是说&#xff0c;我多出来的一个资源&#xff08;11个资源5个进程&#xff0c;每个进程分配2个&#xff09;给其中一个进程之后&#xff0c;运行完了资源释放…

【源码篇】基于SpringBoot+thymeleaf实现的图书管理系统

系统介绍 基于SpringBootthymeleaf实现的图书管理系统分为管理员、读者两个登录角色&#xff0c;一共是8个功能模块 管理员权限 图书管理&#xff1a; 添加图书&#xff1a;书名、作者、出版社、ISBM、简介、价格、出版日期、分类、数量查询图书&#xff1a;根据书名或分类…

Tomcat发布成服务

一、配置服务 1.配置bin目录下的service.bat&#xff08;用于生成新服务&#xff09; 配置内容&#xff1a; rem Tomcat解压根目录 set CATALINA_HOMED:\apache-tomcat-7.0.55-8080 rem 服务显示名称&#xff08;服务中对外显示的名称&#xff09; set PR_DISPLAYNAMEapache…

ChunJun FTP Connector 功能扩展解读

本文将从 FTP Connector 的功能详解&#xff0c;自定义文件切割及自定义 FileFormat 三个方面为大家带来 ChunJun FTP Connector 的功能扩展分享。 FTP Connector 详解 FTP 是用于在网络上进行文件传输的一套标准协议&#xff0c;它工作在 OSI 模型的第七层&#xff0c; TCP …

从0到1搭建自己的脚手架(java后端) | 京东云技术团队

一、脚手架是什么 脚手架是一种基础设施工具&#xff0c;用于快速生成项目的框架代码和文件结构。它是一种标准化的开发工具&#xff0c;使开发人员能够在项目的早期阶段快速搭建出一个具备基本功能和结构的系统。 二、脚手架的意义 主流的微服务架构体系下很多公司会将原有…

HarmonyOS/OpenHarmony应用开发-ArkTS语言基本语法说明

以一个具体的示例来说明ArkTS的基本组成。如下图所示&#xff0c;当开发者点击按钮时&#xff0c;文本内容从“Hello World”变为“Hello ArkUI”。 图1 示例效果图 本示例中&#xff0c;ArkTS的基本组成如下所示。 图2 ArkTS的基本组成 装饰器&#xff1a; 用于装饰类、结构…

impala中group_concat()函数无法对内容进行order by

描述&#xff1a; 使用的是impala数据库&#xff0c;假设有四笔数据&#xff0c;是无序的&#xff0c;业务上要求将其行转列成一行数据&#xff0c;并且里面的数据要按从小到大排序。 过程&#xff1a; 猜测&#xff1a; 数据库Oracle、Mysql、MSsql等支持group_concat中使…

依赖倒置原则:高层代码和底层代码,到底谁该依赖谁?

前言 上一篇&#xff0c;我们讲了 ISP 原则&#xff0c;知道了在设计接口的时候&#xff0c;我们应该设计小接口&#xff0c;不应该让使用者依赖于用不到的方法。 依赖这个词&#xff0c;程序员们都好理解&#xff0c;意思就是&#xff0c;我这段代码用到了谁&#xff0c;我就…

签名支持全球管控AI 三巨头侧漏“求生欲”

又一封“群星云集”警示AI风险的公开信来了&#xff0c;这封信的内容简短但措辞炸裂&#xff1a;减轻 AI 带来的灭绝风险&#xff0c;应该与管控流行病和核战争等其他社会级规模的风险一样&#xff0c;成为一项全球优先事项。 5月30日&#xff0c;这纸原文只有22个单词的声明&…

核心交换机的四种关键技术:链路聚合、冗余、堆叠和热备份,真简单!

你好&#xff0c;这里是网络技术联盟站。 当涉及到核心交换机的关键技术&#xff0c;如链路聚合、冗余、堆叠和热备份时&#xff0c;下面更详细地介绍每个技术的工作原理和优势。 1. 链路聚合 链路聚合是一种技术&#xff0c;用于将多个物理链路组合成一个逻辑链路&#xff0…

详解Java枚举

一、知识点 二、概念 enum 的全称为 enumeration&#xff0c; 是 JDK 1.5 中引入的新特性。 在Java中&#xff0c;被 enum 关键字修饰的类型就是枚举类型。形式如下&#xff1a; enum Color { RED, GREEN, BLUE }如果枚举不添加任何方法&#xff0c;枚举值默认为从0开始的有…

CSS 选择器的常见用法

前言 CSS在编写代码的时候有很多种样式&#xff0c;和和HTML&#xff0c;JS相似&#xff0c;他们都是运行在浏览器中的&#xff0c;下面就介绍一下CSS选择器的常见用法。 标签选择器使用标签名把页面中所有同名标签都选中类选择器使用.类名的方式对应一组CSS属性id选择器使用 …

小米再度登上《焦点访谈》!中关村论坛展科技风采

5月30日下午&#xff0c;以“开放合作共享未来”为主题的2023中关村论坛展览&#xff08;科博会&#xff09;在京圆满落幕。小米作为科技领军企业参展&#xff0c;设立“科技创新、绿色低碳”主题展区。 小米携智能手机、可穿戴设备、智能家居以及全尺寸人形仿生机器人CyberOne…

【ROS】ROS2编程示例:话题订阅-发布-C++版

1、准备 1&#xff09;安装ROS2 【ROS】Ubuntu22.04安装ROS2&#xff08;Humble Hawksbill&#xff09; 2&#xff09;ROS2命令 【ROS】ROS2命令行工具详解 3&#xff09;配置工作空间 【ROS】ROS2中的概念和名词解释中第一节&#xff1a;工作空间 workspace 4&#xff09;…

MySQL-12-SQL优化

一、MySQL体系结构 1.1、体系结构 # 1.2、查询执行流程 参考&#xff1a;https://www.cnblogs.com/xfeiyun/p/15899990.html 1.3、组件说明 管理工具&#xff1a;MySQL服务软件安装后提供的命令连接池&#xff1a;检查本机是否有空闲资源&#xff08;线程&#xff0c;内存&…

Geoffrey Hinton、姚期智、张钹、Sam Altman等专家共话AI安全与对齐丨2023智源大会议程公开...

6月9日&#xff0c;2023北京智源大会&#xff0c;将邀请AI领域的探索者、实践者、以及关心智能科学的每个人&#xff0c;共同拉开未来舞台的帷幕&#xff0c;你准备好了吗&#xff1f;与会知名嘉宾包括&#xff0c;图灵奖得主Yann LeCun、OpenAI创始人Sam Altman、图灵奖得主Ge…

Linux教程——Linux和UNIX的关系及区别(详解版)

UNIX 与 Linux 之间的关系是一个很有意思的话题。在目前主流的服务器端操作系统中&#xff0c;UNIX 诞生于 20 世纪 60 年代末&#xff0c;Windows 诞生于 20 世纪 80 年代中期&#xff0c;Linux 诞生于 20 世纪 90 年代初&#xff0c;可以说 UNIX 是操作系统中的"老大哥&…

开始梳理大学课程体系(二)--万字数据结构总结上

数据结构总结 第一章 概述1.1 基本概念和术语1.2 数据结构1. 2.1 逻辑结构1.2.2 存储结构 1.3 数据类型和抽象数据类型1.3.1 数据类型1.3.2 抽象数据类型 1.4 算法和算法分析1.4.1 算法的定义及特性1.4.2 评价算法优劣的基本标准1.4.3 算法的时间复杂度1.4.4 算法的空间复杂度 …

chatgpt赋能python:用Python优化交通

用Python优化交通 作为一种功能强大的编程语言&#xff0c;Python已经被广泛应用于各个领域。交通领域也不例外。在交通领域&#xff0c;Python可以发挥重要作用&#xff0c;帮助优化交通运行&#xff0c;提高安全性和效率。 实时路况预测 Python可以通过机器学习算法来对实…