线程池核心原理浅析

news2024/11/27 2:28:25

前言

由于系统资源是有限的,为了降低资源消耗,提高系统的性能和稳定性,引入了线程池对线程进行统一的管理和监控,本文将详细讲解线程池的使用、原理。


为什么使用线程池

池化思想

线程池主要用到了池化思想,池化思想在计算机领域十分常见,主要用于减少资源浪费、提高性能等。

池化思想主要包含以下几个方面:

fuxing

一些常见的资源池包括线程池、数据库连接池、对象池、缓存池、连接池等。

池化思想可以提高系统的性能,因为它减少了资源的创建和销毁次数,避免了不必要的开销。通过池化,系统可以更好地应对高并发情况,降低资源竞争,提高响应速度。

什么是线程池

根据池化思想,在一个系统中,为了避免线程频繁的创建和销毁,让线程可以复用,引入了线程池的概念。线程池中,总有那么几个活跃线程。

当你需要使用线程时,可以从池子中随便拿一个空闲线程,当完成工作时,并不急着关闭线程,而是将这个线程退回到池子,方便其他人使用。

简单说就是,在使用线程池后,创建线程变成了从线程池中获得空闲线程,关闭线程编程了向池子里归还线程。

大致流程如下:

fuxing

## 为什么使用线程池 Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。

在开发过程中,合理地使用线程池能够带来3个好处。

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

要做到合理利用线程池,必须对其实现原理了如指掌。

线程池的使用

fuxing

## ThreadPoolExecutor ThreadPoolExecutor 的创建方法总体来说可分为 2 种:

  • 通过 ThreadPoolExecutor 构造函数
  • 通过 Executors 类创建

通过构造函数

1.1. 入参含义

这个也是推荐使用的方法,因为通过 Executors 类创建可能会导致 OOM,如下图阿里开发规范中的描述。

fuxing

构造函数入参:

 
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

构造函数入参含义:

fuxing

1.2. 阻塞队列

workQueue 可选的 BlockingQueue:

fuxing

1.3. 拒绝策略

fuxing

如下图,上述拒绝策略均实现 RejectedExecutionHandler 接口,且为 ThreadPoolExecutor 的内部类。

fuxing

若以上策略仍无法满足实际应用需要,完全可以自已扩展 RejectedExecutionHandler 接口。

 
public interface RejectedExecutionHandler {
/**
* @param r 当前请求执行的任务
* @param executor 当前的线程池
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

示例:

 
public class RejectedExecutionDemo {
public static class MyTask implements Runnable{
@Override
public void run() {
System.out.println(new Date() + ":Thread ID is" + Thread.currentThread().getId());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws InterruptedException {
MyTask myTask = new MyTask();
ExecutorService executorService = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
(r, executor) -> System.out.println(r.hashCode() + "is discard")
);
for (int i = 0; i < 100; i++) {
executorService.submit(myTask);
Thread.sleep(10);
}
}
}

上述示例中,mytask 执行需要花费100毫秒,因此,必然会导致一些任务被直接丢弃。在实际应用中,我们可以将更详细的信息记录到日志中,来分析任务丢失情况和系统负载。

fuxing

通过 Executors

Executors 类扮演着线程池工厂的角色,通过该类可以取得一个拥有定功能的线程池。

该类可以创建三种类型的 ThreadPoolExecutor:

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool
2.1. FixedThreadPool

固定线程数的线程池,该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂时存在任务队列中,待有线程空闲时,在处理队列中的任务。

FixedThreadPool 使用的无界任务队列 LinkedBlockingQueue,可能造成内存泄露。

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

只有一个工作线程的线程池,当多于 1 个任务被提交时,会存到任务队列中。该线程池使用的无界任务队列 LinkedBlockingQueue,可能造成内存泄露。

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

根据实际情况调整线程数的线程池,线程池的线程数量不确定,若有空闲线程可复用,则会优先使用。若所有线程均在工作,此时新的任务则会创建新的线程优先处理。所有线程在任务执行完毕后,将返回线程池进行复用。

corePoolSize 被设置为0,maximumPoolSize 被设置为无界,存活时间设置为 60s,空闲线程超过60秒后将会被
终止。极端情况线程创建过多,会导致内存泄露。

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

ScheduledThreadPoolExecutor

简介

如下图, ScheduledThreadPoolExecutor 继承自ThreadPoolExecutor,它主要用来定期执行任务,功能与 Timer 类似且更加强大,可以在构造函数中指定多个对应的后台线程数。

fuxing

使用

可通过 Executors 创建,源码如下:

 
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

这里的返回值是 ScheduledExecutorService,根据时间对线程进行调度。有三个主要方法:

 
public interface ScheduledExecutorService extends ExecutorService {
/**
* 给定时间对任务进行调度
*/
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
/**
* 周期性对任务进行调度
* 以第一个任务的开始时间 initialDelay + period
* 第一个任务在 initialDelay + period 执行
* 第二个任务在 initialDelay + period * 2 执行
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
/**
* 周期性对任务进行调度
* 上一个任务结束后,再经过 period 时间开始执行
*/
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}

如果任务遇到异常,那么后续的所有子任务都会停止调度,因此,必须保证异常被及时处理,为周期性任务的稳定调度提供条件。

ForkJoinPool

fork 是开启子进程,join 是等待,意思是分支子进程结束后才能得到结果,实际开发中,若频繁的 fork 开启线程可能严重影响系统性能,所以引入了 ForkJoinPool。

大致流程是,向 ForkJoinPool 线程池中提交一个 ForkJoinTask 任务,就是将任务分解成多个小任务,等任务全部完成后进行处理,这里采用了分治的思想,具体我将在后续单独展开,这里不多做赘述。

ForkJoin 可能出现两个问题:

  1. 子线程积累过多,可能导致系统性能严重下降;
  2. 调用层次过深,可能导致栈溢出。

线程池的任务提交

execute()

该方法用于提交不需要返回值的任务,且无法判断任务是否被线程池执行成功。

源码见下面的线程池原理章节。

submit()

该方法用于提交需要返回值的任务。线程池会返回 Future 对象,可以判断任务是否执行成功,还可以通过 Future 的get()方法来获取返回值。

get()方法会阻塞当前线程直到任务完成,还可以设置超时时间,到时立即返回,不过这时有可能任务没有执行完。

 
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}

线程池的关闭

可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池。

它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt() 来中断线程,所以无法响应中断的任务可能永远无法终止。

两种方法存在一定的区别,shutdownNow首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。而 shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,表示线程池关闭成功,这时调用isTerminaed方法会返回true。

至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow 方法。

线程池执行原理

执行源码

 
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果当前工作线程数是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 添加核心线程去执行任务,成功则return
if (addWorker(command, true))
return;
// 添加失败,ctl有变化,需重新获取
c = ctl.get();
}
// 判断是否为RUNNING,此时核心线程数已满,需加入任务队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 检查若不是RUNNING则将任务从队列移除
if (! isRunning(recheck) && remove(command))
// 执行拒绝策略
reject(command);
// 正常则添加一个非核心空线程,执行队列中的任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 表示核心线程满了,队列也满了,创建非核心线程,执行任务
else if (!addWorker(command, false))
// 最大线程数也满了,走拒绝策略
reject(command);
}

流程图

fuxing


参考:
[1] 魏鹏. Java并发编程的艺术.
[2] 葛一鸣/郭超. 实战Java高并发程序设计.

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

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

相关文章

vivado 低级别 SVF JTAG 命令

低级别 SVF JTAG 命令 注释 &#xff1a; 在 Versal ™ 器件上不支持 SVF 。 低级别 JTAG 命令允许您扫描多个 FPGA JTAG 链。针对链操作所生成的 SVF 命令使用这些低级别命令来访问链中的 FPGA 。 报头数据寄存器 (HDR) 和报头指令寄存器 (HIR) 语法 HDR length […

健康知识集锦

页面 页面代码 <% layout(/layouts/default.html, {title: 健康知识管理, libs: [dataGrid]}){ %> <div class"main-content"><div class"box box-main"><div class"box-header"><div class"box-title"&g…

CDGA|电子行业数据治理六大痛点及突围之道

CDGA|电子行业数据治理六大痛点及突围之道 随着信息技术的迅猛发展&#xff0c;电子行业对数据的需求和依赖日益增强。然而&#xff0c;数据治理作为确保数据质量、安全性及有效利用的关键环节&#xff0c;在电子行业中却面临着一系列痛点。本文将深入探讨电子行业数据治理的六…

基于LMV358的负电源架构

嘿UU们&#xff0c;中午好啊&#xff01;吃了没&#xff1f;算算时间我的餐桌上应该快上杨梅和鱼胶冻了。 今天看某群&#xff0c;突然想到Jim williams的书里一个架构&#xff0c;但老爷子的东西是正负输出的&#xff0c;而且略微有点麻烦&#xff0c;我就想怎么样整个更适合…

实现网站HTTPS访问:全面指南

在当今网络安全至关重要的时代&#xff0c;HTTPS已经成为网站安全的基本标准。HTTPS&#xff08;超文本传输安全协议&#xff09;通过在HTTP协议基础上加入SSL/TLS加密层&#xff0c;确保了数据在用户浏览器和服务器之间的传输是加密的&#xff0c;有效防止数据被窃取或篡改&am…

专题六_模拟(2)

目录 6. Z 字形变换 解析 题解 38. 外观数列 解析 题解 6. Z 字形变换 6. Z 字形变换 - 力扣&#xff08;LeetCode&#xff09; 解析 题解 class Solution { public:string convert(string s, int numRows) {// 42.专题六_模拟_N 字形变换_C// 处理边界情况if (numRows …

多线程典型例子(4)——线程池

文章目录 一、线程池的基本情况1.1、使用线程池的必要性1.2、线程池为什么比直接在系统中创建线程更高效&#xff1f;1.2.1、纯内核态操作1.2.2、纯用户态操作 1.3、那为什么用户态操作比内核态操作更高效&#xff1f;二、如何在Java中使用线程池2.1、ExecutorService2.1、Thre…

常见JavaWeb混合Vue.js课设中的要点

在校期间我们要做很多课设&#xff0c;实际上&#xff0c;学校教的大概率不足以让多数学生独立做出系统。在网上随便一搜&#xff0c;大抵都是千篇一律的“XXXX”管理系统。这些项目出于方便&#xff0c;往往采用vue作为前端框架而不用原生的JavaScript。 vue的本质要点是避免原…

FPGA HDMI Sensor无线航模摄像头

FPGA方案&#xff0c;接收摄像头sensor 图像数据后&#xff0c;通过HDMI输出到后端 客户应用&#xff1a;无线航模摄像头 主要特性&#xff1a; 1.支持2K以下任意分辨率格式 2.支持多种型号sensor 3.支持自适应摄像头配置&#xff0c;并补齐输出时序 4.可定制功能&#xff…

一文详解|影响成长的关键思考(二)

之前写过一篇《一文详解&#xff5c;影响成长的关键思考》&#xff0c;里面对自己工作前几年的心法进行了总结&#xff0c;并分享了出来。现在又工作了一段时间后&#xff0c;有了一些新的体会&#xff0c;想进一步分析一下&#xff0c;于是便有了此文。的确&#xff0c;思考也…

2024服贸会,参展企业媒体宣传报道攻略

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 2024年中国国际服务贸易交易会&#xff08;简称“服贸会”&#xff09;是一个重要的国际贸易平台&#xff0c;对于参展企业来说&#xff0c;有效的媒体宣传报道对于提升品牌知名度、扩大…

docker学习笔记(五):harbor仓库搭建与简单应用

harbor私有仓库 简介 Docker容器应用的开发和运行离不开可靠的镜像管理&#xff0c;虽然Docker官方也提供了公共的镜像仓库&#xff0c;但是从安全和效率等方面考虑&#xff0c;部署私有环境内的Registry也是非常必要的。Harbor是由VMware公司开源的企业级的Docker Registry管…

【快捷部署】022_ZooKeeper(3.5.8)

&#x1f4e3;【快捷部署系列】022期信息 编号选型版本操作系统部署形式部署模式复检时间022ZooKeeper3.5.8Ubuntu 20.04tar包单机2024-05-07 一、快捷部署 #!/bin/bash ################################################################################# # 作者&#xff…

晶片的厚度会影响晶振的频率吗?

晶振&#xff0c;是一种能够产生稳定频率的电子元件&#xff0c;广泛应用于各种电子设备中。晶振的频率参数&#xff0c;即其振荡产生的频率大小&#xff0c;是晶振性能的重要指标之一。石英晶体的切割方式显得至关重要。不同的切割方式&#xff0c;如AT-cut、CT-cut、SC-cut等…

实力再获认可!WeTrade荣获“最佳交易流动性”大奖

WeTrade在泰国举行的颁奖典礼上荣获“最佳交易流动性”奖项。 颁奖典礼于2024年4月28日在曼谷 Grande Centre Point Surawong 隆重举行&#xff0c;与FastBull 2024交易影响力颁奖典礼同期举办。FastBull是一家全球领先的金融资讯平台&#xff0c;覆盖全球多市场金融服务&…

推荐4个可用的github国内镜像

Github是全球最大的代码托管云平台&#xff0c;超过1亿用户在平台上分享代码及数据&#xff0c;深受生物信息学软件开发者的喜爱&#xff0c;并且现在发表文章&#xff0c;若涉及到代码&#xff0c;编辑还要求我们把代码及数据存放在github上&#xff0c;以便检查数据的真实性和…

风电齿轮箱轴承为风电齿轮箱核心部件 滑动轴承为其主要类型

风电齿轮箱轴承为风电齿轮箱核心部件 滑动轴承为其主要类型 风电齿轮箱轴承全称为风力发电机组齿轮箱轴承&#xff0c;为风电齿轮箱核心部件&#xff0c;起到减少摩擦损失、支撑齿轮等作用。风电齿轮箱轴承具备耐腐蚀、可靠性高、体积小、使用寿命长等优势&#xff0c;在大型风…

SparkStructuredStreaming状态编程

spark官网关于spark有状态编程介绍比较少&#xff0c;本文是一篇个人理解关于spark状态编程。 官网关于状态编程代码例子: spark/examples/src/main/scala/org/apache/spark/examples/sql/streaming/StructuredComplexSessionization.scala at v3.5.0 apache/spark (github…

华为OD机试 - 手机App防沉迷系统(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

初识Java的main方法

创建一个Java文件 main方法以及用cmd运行程序的过程 面试题JDK\JRE\JVM之间的关系 注意事项 解析String[ ] args 我们想知道String[ ] args里面到底是什么&#xff0c;我们可以用for循环遍历这个数组 Java代码结构 编写Java程序时可能会遇见的错误 注释 注释是为了让代码更…