规范使用线程池与底层原理详解

什么是线程池

“线程池”顾名思义,就是存放线程的池子,这个池子可以存放多少线程取决于采用哪种线程池,取决于有多少并发线程,有多少计算机的硬件资源。

线程池优势

线程池最主要的工作在于控制运行线程的数量,从而做到线程复用、控制最大并发数量、管理线程。其具体的优势在于:

  • 降低资源消耗:通过重复利用已经创建的线程降低线程创建和销毁造成的消耗;
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能执行;
  • 提高线程的可管理性:线程是稀缺资源,不能无限创建,否则会消耗系统资源、降低系统的稳定性,使用线程可以进行统一分配,调优和监控;

如何创建线程池

线程池继承结构图:

规范使用线程池与底层原理详解

jdk自带创建线程池的四种常见方式:

  • Executors.newFixedThreadPool(int):创建一个固定线程数量的线程池,可控制线程最大并发数,超出的线程需要在队列中等待。注意它内部corePoolSize和maximumPoolSize的值(就是第一和第二个参数 nThreads)是相等的,并且使用的是LinkedBlockingQueue:

源码:

java;gutter:true; public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }</p> <pre><code> * Executors.newSingleThreadExecutor():创建一个单线程的线程池,它只有唯一的线程来执行任务,保证所有任务按照指定顺序执行。注意它内部corePoolSize和maximumPoolSize的值都为1,它使用的是LinkedBlockingQueue: **源码:** ;gutter:true;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}

  • Executors.newCachedThreadPool():创建一个可缓存的线程池,如果线程长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则创建新线程。注意它内部将corePoolSize值设为0,maximumPoolSize值设置为Integer.MAX_VALUE,并且使用的是SynchronizedQueue,keepAliveTime值为60,即当线程空闲时间超过60秒,就销毁线程:

源码:

java;gutter:true; public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); }</p> <pre><code> * Executors.newScheduledThreadPool(int):创建一个固定线程数量的线程池,相比于newFixedThreadPool(int)固定个数的线程池强大在 ①可以执行延时任务,②也可以执行带有返回值的任务,并且使用的是DelayedWorkQueue: **源码:** ;gutter:true;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
} | | | | | | | | | | | |
V V V V V V V V V V V V
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
| | new DelayedWorkQueue());
} | |
V V //ScheduledThreadPoolExecutor继承了ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

注意:

  • 以上四种创建线程的方式内部都是由ThreadPoolExecutor这个类完成的,该类的构造方法有5个参数,称为线程池的5大参数(还有另外两个参数);
  • 线程池使用完毕之后需要关闭,应该配合try-finally代码块,将线程池关闭的代码放在finally代码块中;

线程池的7大参数

ThreadPoolExecutor对构造函数进行了重载,实际内部使用了7个参数:

java;gutter:true; public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ..............//已省略 参数合法校验 ..............//已省略 参数赋值 }</p> <pre><code> * corePoolSize:线程池中常驻核心线程池(当线程池中的线程数目达到了corePoolSize后,就会把任务放到缓存队列中;) * maximumPoolSize:线程池中能够容纳同时执行最大线程数,该值必须大于等于1 * keepAliveTime:多余线程的最大存活时间 * unit:keepAliveTime的单位 * workQueue:任务队列,被提交但尚未被执行的任务(阻塞队列) * threadFactory:生成线程池中工作线程的线程工厂,一般使用默认即可 * handler:拒绝策略,表示当任务队列满并且工作线程大于等于线程池的最大线程数时,对即将到来的线程的拒绝策略 线程池底层原理 ![规范使用线程池与底层原理详解](https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/1483369-20200812145552758-767360613.png) **线程池具体工作流程:** * 在创建线程后,等待提交过来的任务请求 * 当调用execute()/submit()方法添加一个请求任务时,线程池会做出以下判断: - 如果正在运行的线程数量小于corePoolSize,会立刻创建线程运行该任务 - 如果正在运行的线程数量大于等于corePoolSize,会将该任务放入阻塞队列中 - 如果队列也满但是正在运行的线程数量小于maximumPoolSize,线程池会进行拓展 - 将线程池中的线程数拓展到最大线程数 - 如果队列满并且运行的线程数量大于等于maximumPoolSize,那么线程池会启动相应的拒绝策略来拒绝相应的任务请求 * 当一个线程完成任务时,它会从队列中取下一个任务来执行 * 当一个线程空闲时间超过给定的keepAliveTime时,线程会做出判断: - 如果当前运行线程大于corePoolSize,那么该线程将会被停止。也就是说,当线程池的所有任务都完成之后,它会收缩到corePoolSize的大小 线程池的拒绝策略 当线程池的阻塞队列满了同时线程池中线程数量达到了最大maximumPoolSize时,线程池将会启动相应的拒绝策略来拒绝请求任务。 **4种拒绝策略具体为:** * AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行 * CallerRunsPolicy:调用者运行的一种机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者 * DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中尝试再次提交当前任务 * DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果任务允许丢失,那么该策略是最好的方案 **注意:**以上4种拒绝策略均实现了RejectedExecutionHandler接口 规范创建线程池 实际开发中不允许使用内置的线程池:必须明确地通过 **ThreadPoolExecutor**方式,指定相应的线程池参数创建自定义线程或者使用其它框架提供的线程池。因为内置线程池的第五个参数阻塞队列允许的请求队列长度为 **Integer.MAX_VALUE**(从上面的源码上可以看出),可能造成大量请求堆积,导致OOM: **阿里巴巴规范中指出不能使用Executors去创建:** ![规范使用线程池与底层原理详解](https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/1483369-20200812150422793-800097172.png) 自定义线程池:使用不同的拒绝策略: ;gutter:true;
package com.raicho.mianshi.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* @author: Raicho
* @Description: 自定义线程池的各个参数
* @program: mianshi
* @create: 2020-08-12 10:44
**/
public class CustomThreadPool {
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(
2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
// new ThreadPoolExecutor.AbortPolicy()
new ThreadPoolExecutor.CallerRunsPolicy() // 注意使用该拒绝策略,可能会回退给main线程执行
// new ThreadPoolExecutor.DiscardOldestPolicy()
//new ThreadPoolExecutor.DiscardPolicy()

);
try {
for (int i = 0; i < 9; i++) {
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + ": 执行任务");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}

线程池配置合理线程数量

线程池合理配置线程数量需要考虑业务具体是CPU密集型还是IO密集型:

  • CPU密集型:该任务需要大量运算,而没有阻塞,CPU一直在全速运行,CPU密集型只有在真正的多核CPU上才能进行加速。

CPU密集型任务配置应该尽可能少的线程数量,一般公式为:

java;gutter:true; CPU核数 + 1个线程的线程池</p> <pre><code> * IO密集型:任务需要大量的IO操作,即大量的阻塞。在单线程上进行IO密集型的任务会浪费大量的CPU运算能力在等待操作上。 所以在IO密集型任务中使用多线程可以大大加速程序运行: ;gutter:true;
CPU核数 / (1 – 阻塞系数) 阻塞系数在0.8-0.9
CPU核数 * 2

Original: https://www.cnblogs.com/raicho/p/13490680.html
Author: Raicho
Title: 规范使用线程池与底层原理详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581391/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Nginx 的基本概念

    Nginx 简介 什么是 Nginx Nginx 是一个高性能的 HTTP 和 反向代理 web服务器 占用内存少,并发能力强,高性能,热部署 但不支持 Java,Java 得配合…

    Java 2023年6月13日
    065
  • 三、《微服务:从设计到部署》–进程间通信IPC

    交互方式: 在单体应用中,组件可通过语言级方法或者函数相互调用。相比之下,基于微服务的应用是一个运行在多台机器上的分布式系统。通常,每个服务实例都是一个进程。因此,服务必须使用进程…

    Java 2023年6月5日
    0100
  • 【转载】VMware虚拟机NAT模式网络配置图文教程

    原文:https://blog.csdn.net/dingguanyi/article/details/77829085 一、引言在Windows上搭建集群实验环境时,为能够让集群…

    Java 2023年5月30日
    0113
  • JavaSE笔记

    文末有Gitee链接,记得star哦课程整体内容概述 第一部分:编程语言核心结构主要知识点:变量、基本语法、分支、循环、数组、第二部分:Java面向对象的核心逻辑主要知识点:OOP…

    Java 2023年6月13日
    070
  • Collections.singletonList方法

    这个方法主要用于只有一个元素的优化, 减少内存分配,无需分配额外的内存,可以从SingletonList内部类看得出来,由于只有一个element,因此可以做到内存分配最小化,相比…

    Java 2023年6月13日
    084
  • 数据分表Mybatis Plus动态表名最优方案的探索

    一、应用场景 大家在使用Mybatis进行开发的时候,经常会遇到一种情况:按照月份month将数据放在不同的表里面,查询数据的时候需要跟不同的月份month去查询不同的表。 但是我…

    Java 2023年6月15日
    090
  • PTA第4、5次大作业及期中考试总结

    期中考试题目总结 (1)总结:期中考试主要考察的是点线面类的设计,之前没有接触过类,或者说对类没有什么很清晰的概念和认知。现在才发现”类”是一个非常强大的工…

    Java 2023年6月5日
    078
  • CentOS安装Docker

    Docker CE 支持 64 位版本 CentOS 7,并且要求内核版本不低于 3.10 1.1.卸载(可选) 如果之前安装过旧版本的Docker,可以使用下面命令卸载: yum…

    Java 2023年6月7日
    080
  • LeetCode.1217-交换芯片(Play with Chips)

    这是小川的第421次更新,第454篇原创 今天介绍的是 LeetCode算法题中 Easy级别的第 270题(顺位题号是 1217)。There are some chips, a…

    Java 2023年6月5日
    081
  • Spring 源码学习笔记11——Spring事务

    Spring&#x4E8B;&#x52A1;&#x662F;&#x57FA;&#x4E8E;Spring Aop&#x7684;&a…

    Java 2023年6月14日
    081
  • POSIX 线程清理函数

    控制清理函数的函数有两个,一个是 pthread_cleanup_push(), 用来把清理函数压入栈中,另一个是 pthread_cleanup_pop(), 用来把栈中的函数弹…

    Java 2023年5月30日
    074
  • springboot实现读写分离

    转载:springboot实现读写分离(基于Mybatis,mysql) Original: https://www.cnblogs.com/zxf330301/p/1402860…

    Java 2023年5月30日
    097
  • Mybatis Mapper动态代理方式 typeAliases 别名的使用

    目录结构及配置文件与原始dao方法相比更简便 只需一个UserMapper的接口,放在一起的配置文件,配置文件中namespace的地址确定jdk动态代理的对象

    Java 2023年5月30日
    081
  • JdbcTemplate 常用方法

    JdbcTemplate 常用方法update:实现增删改 queryForObject:​ 查询的结果是一个对象或单个值 java;gutter:true; //自己组装对象 p…

    Java 2023年6月13日
    079
  • windows10环境下的RabbitMQ安装步骤(图文)

    第一步:下载并安装erlang 原因:RabbitMQ服务端代码是使用并发式语言Erlang编写的,安装Rabbit MQ的前提是安装Erlang。 下载地址:http://www…

    Java 2023年5月30日
    088
  • 如何在Docker容器中使用Arthas

    Arthas(阿尔萨斯) 能为你做什么? Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。当你遇到以下类似问题而束手无策时, Arthas可以帮助你解决: 这…

    Java 2023年6月9日
    096
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球