线程配置经验
工作时,时常会遇到,线程相关的问题与解法,本人会持续对开发过程中遇到的关于线程相关的问题及解决记录更新记录在此篇博客中。
目录
一、线程基本知识
1. 线程和进程
二、问题与解法
1. 避免乘法级别数量线程并行
1)使用线程池
2)任务队列
3)限制并发任务数量
4)任务分批处理
2. 避免函数被重复调用
1)使用标志位
2)使用同步锁
3)使用AOP和缓存
4)使用消息队列
5)使用限流算法
一、线程基本知识
1. 线程与进程
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。例如,任务管理器显示的就是进程:
线程是比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。
JDK 1.2之前,Java线程是基于绿色线程实现的,这是一种用户级线程。即JVM自己模拟了多线程的运行,而不依赖于操作系统。由于绿色线程和原生线程比起来的在使用时有一些限制(比如,绿色线程不能直接使用操作系统提供的功能如异步 I/O、只能在一个内核线程上运行无法利用多核);在JDK 1.2之后,Java线程改为使用原生线程来实现。也就是说 JVM 直接使用操作系统原生的内核级线程(内核线程)来实现 Java 线程,由操作系统内核进行线程的调度和管理。
- 用户线程:由用户空间程序管理和调度的线程,运行在用户空间(专门给应用程序使用)。
- 内核线程:由操作系统内核管理和调度的线程,运行在内核空间(只有内核程序可以访问)。
顺便简单总结一下用户线程和内核线程的区别和特点:用户线程创建和切换成本低,但不可以利用多核。内核态线程,创建和切换成本高,可以利用多核。现在的 Java 线程的本质其实就是操作系统的线程。
线程模型是用户线程和内核线程之间的关联方式,常见的线程模型有这三种:
- 一对一(一个用户线程对应一个内核线程)
- 多对一(多个用户线程映射到一个内核线程)
- 多对多(多个用户线程映射到多个内核线程)
在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,也就是一个 Java 线程对应一个系统内核线程。
一个进程中可以有多个线程,多个线程共享进程的堆和方法区(JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
- 程序计数器为什么私有:程序计数器是为了线程切换后,能够回到正确的执行位置。
- 虚拟机栈为什么私有:每个Java栈帧在执行之前会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至完成的过程,对应着一个栈帧在Java虚拟机栈中入栈和出栈的过程。
- 本地方法栈为什么私有:和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象(几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
二、问题与解法
1. 避免乘法级别数量线程并行
当一个多线程函数被重复调用,就造成了并行线程数量成为乘法级别。
这种情况通常不是故意的,而是不小心。
一旦发生这种情况,需要及时排查,不然线程太多、资源混乱,服务器上其他服务请求被搁置,就变成了事故。
例如:一个大批量执行、耗时、数据量大的任务。现在使用三个线程、并行生成结果。这个函数 每三分钟执行一次。可能下一次调用开始时,旧的线程还没执行结束。
count (并行线程数)= m(调用次数) * n(单词调用线程数)
乘法级别的线程在处理这些数据,不仅不能提高处理效率、反而由于线程太多造成了资源混乱、大量资源浪费。导致正常的请求无法及时被相应,影响到服务器上其他服务的正常使用。甚至可能导致服务器宕机。
可以使用线程池、任务队列、限制并发任务数量、任务分批处理来解决。
1)使用线程池
线程池可以有效管理线程的生命周期,避免频繁创建和销毁线程带来的开销,并且可以限制并发线程的数量。Java提供了 ExecutorService 来实现线程池。
注意,线程池多线程调用也会出现乘法级别数量的线程
示例代码:
import java.util.concurrent.*;public class TaskScheduler {private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);private final ExecutorService threadPool = Executors.newFixedThreadPool(3);public void scheduleTask(Runnable task) {scheduler.scheduleAtFixedRate(() -> {try {// 提交任务到线程池for (int i = 0; i < 3; i++) {threadPool.submit(task);}} catch (RejectedExecutionException e) {// 处理线程池拒绝任务的情况System.out.println("Task rejected, possibly due to shutdown or overload.");}}, 0, 3, TimeUnit.MINUTES);}public void shutdown() {scheduler.shutdown();threadPool.shutdown();}public static void main(String[] args) {TaskScheduler scheduler = new TaskScheduler();Runnable task = () -> {// 你的任务逻辑System.out.println("Task executed by " + Thread.currentThread().getName());};scheduler.scheduleTask(task);// 程序结束时关闭线程池Runtime.getRuntime().addShutdownHook(new Thread(scheduler::shutdown));}
}
优点:
-
限制线程数量:通过固定大小的线程池,可以避免线程过多导致的资源竞争。
-
任务调度:
ScheduledExecutorService
可以定时执行任务,避免任务重叠。 -
优雅关闭:通过
shutdown()
方法可以优雅地关闭线程池。
2)任务队列
如果任务的执行时间不确定,可以使用任务队列来管理任务。线程池会从任务队列中获取任务并执行。
import java.util.concurrent.*;public class TaskQueueExample {private final ExecutorService threadPool = Executors.newFixedThreadPool(3);private final BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();public void addTask(Runnable task) {try {taskQueue.put(task);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}public void startProcessing() {for (int i = 0; i < 3; i++) {threadPool.submit(() -> {while (!Thread.currentThread().isInterrupted()) {try {Runnable task = taskQueue.take();task.run();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}});}}public void shutdown() {threadPool.shutdown();}public static void main(String[] args) throws InterruptedException {TaskQueueExample example = new TaskQueueExample();example.startProcessing();// 模拟添加任务for (int i = 0; i < 10; i++) {example.addTask(() -> {System.out.println("Task executed by " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟耗时任务} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 程序结束时关闭线程池Runtime.getRuntime().addShutdownHook(new Thread(example::shutdown));}
}
优点:
-
任务排队:任务会被放入队列,按顺序执行,避免任务重叠。
-
线程复用:线程池中的线程会复用,减少线程创建和销毁的开销。
3)限制并发任务数量
如果任务的执行时间较长,可以限制同时执行的任务数量。例如,每次只允许 3 个任务并发执行。
示例代码:
import java.util.concurrent.*;public class LimitedConcurrencyExample {private final Semaphore semaphore = new Semaphore(3);private final ExecutorService threadPool = Executors.newFixedThreadPool(3);public void addTask(Runnable task) {threadPool.submit(() -> {try {semaphore.acquire(); // 获取许可task.run();} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {semaphore.release(); // 释放许可}});}public void shutdown() {threadPool.shutdown();}public static void main(String[] args) {LimitedConcurrencyExample example = new LimitedConcurrencyExample();for (int i = 0; i < 10; i++) {example.addTask(() -> {System.out.println("Task executed by " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟耗时任务} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 程序结束时关闭线程池Runtime.getRuntime().addShutdownHook(new Thread(example::shutdown));}
}
优点:
-
限制并发:通过
Semaphore
限制同时执行的任务数量。 -
线程复用:线程池中的线程会复用,减少线程创建和销毁的开销。
4)任务分批处理
如果任务数据量很大,可以将任务分批处理,每批任务由一个线程处理。
示例代码:
import java.util.concurrent.*;public class BatchTaskExample {private final ExecutorService threadPool = Executors.newFixedThreadPool(3);public void processTasks(List<Runnable> tasks) {int batchSize = tasks.size() / 3;for (int i = 0; i < 3; i++) {int start = i * batchSize;int end = (i == 2) ? tasks.size() : start + batchSize;threadPool.submit(() -> {for (int j = start; j < end; j++) {tasks.get(j).run();}});}}public void shutdown() {threadPool.shutdown();}public static void main(String[] args) {BatchTaskExample example = new BatchTaskExample();List<Runnable> tasks = new ArrayList<>();for (int i = 0; i < 10; i++) {tasks.add(() -> {System.out.println("Task executed by " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟耗时任务} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}example.processTasks(tasks);// 程序结束时关闭线程池Runtime.getRuntime().addShutdownHook(new Thread(example::shutdown));}
}
优点:
-
分批处理:将任务分批处理,避免任务重叠。
-
线程复用:线程池中的线程会复用,减少线程创建和销毁的开销。
总结
根据你的需求,可以选择以下方案:
-
使用线程池:限制线程数量,避免线程过多导致的资源竞争。
-
任务队列:将任务放入队列,按顺序执行,避免任务重叠。
-
限制并发任务数量:通过
Semaphore
限制同时执行的任务数量。 -
任务分批处理:将任务分批处理,每批任务由一个线程处理。
选择合适的方案可以有效解决线程过多导致的问题,提高任务执行的效率。
2. 避免函数被重复调用
Java中,可以通过多种方式避免函数被重复调用。以下是常见的方法
1)使用标志位
函数调用前,设置一个标志位表示该函数是否已经被调用过。如果已经被调用多,则不再重复调用。
public class FunctionCall {private static boolean isCalled = false;public static void function() {if (isCalled) {System.out.println("Function is already being called.");return;}isCalled = true;try {// Function logic hereSystem.out.println("Function is being called.");} finally {isCalled = false;}}public static void main(String[] args) {new Thread(() -> function()).start();new Thread(() -> function()).start();}
}
2)使用同步锁
使用同步锁(如 synchronized
或 ReentrantLock
)来确保函数在同一时间只能被一个线程调用。
import java.util.concurrent.locks.ReentrantLock;public class FunctionCall {private static final ReentrantLock lock = new ReentrantLock();public static void function() {lock.lock();try {// Function logic hereSystem.out.println("Function is being called.");} finally {lock.unlock();}}public static void main(String[] args) {new Thread(() -> function()).start();new Thread(() -> function()).start();}
}
3)使用AOP和缓存
通过AOP(面向切面编程)和缓存来检测函数是否正在被调用。如果函数正在被调用,则不再重复调用。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic CacheManager cacheManager() {return new ConcurrentMapCacheManager("functionCache");}
}@Aspect
public class FunctionCallAspect {private final CacheManager cacheManager;public FunctionCallAspect(CacheManager cacheManager) {this.cacheManager = cacheManager;}@Pointcut("execution(* com.example.FunctionCall.function(..))")public void functionCallPointcut() {}@Around("functionCallPointcut()")public Object aroundFunctionCall(ProceedingJoinPoint joinPoint) throws Throwable {Cache cache = cacheManager.getCache("functionCache");if (cache != null && cache.get("functionKey") != null) {System.out.println("Function is already being called.");return null;}cache.put("functionKey", "inProgress");try {return joinPoint.proceed();} finally {cache.evict("functionKey");}}
}public class FunctionCall {public static void function() {// Function logic hereSystem.out.println("Function is being called.");}public static void main(String[] args) {new Thread(() -> function()).start();new Thread(() -> function()).start();}
}
4)使用消息队列
将函数调用请求放入消息队列中,然后由后台服务从队列中取出并处理,避免了函数的重复调用。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class FunctionCall {private static final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();public static void function() {queue.add(() -> {// Function logic hereSystem.out.println("Function is being called.");});}public static void main(String[] args) {new Thread(() -> {while (true) {try {Runnable task = queue.take();task.run();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}).start();new Thread(() -> function()).start();new Thread(() -> function()).start();}
}
5)使用限流算法
通过限流算法(如漏桶算法或令牌桶算法)来控制函数的调用频率,避免函数的重复调用。
import java.util.concurrent.Semaphore;public class FunctionCall {private static final Semaphore semaphore = new Semaphore(1);public static void function() {try {semaphore.acquire();// Function logic hereSystem.out.println("Function is being called.");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {semaphore.release();}}public static void main(String[] args) {new Thread(() -> function()).start();new Thread(() -> function()).start();}
}
持续更新ing,推荐博客:
- Java 面试指南 | JavaGuide
相关文章:

线程配置经验
工作时,时常会遇到,线程相关的问题与解法,本人会持续对开发过程中遇到的关于线程相关的问题及解决记录更新记录在此篇博客中。 目录 一、线程基本知识 1. 线程和进程 二、问题与解法 1. 避免乘法级别数量线程并行 1)使用线程池…...

火语言RPA--KimiAiFree服务
🚩【组件功能】:KimiAiFree服务支持联网搜索、支持智能体对话、支持长文档解读、支持图像OCR。 配置预览 配置说明 服务地址 支持T或# 自行搭建或第三方提供的KimiAiFree服务地址。 RefreshToken 支持T或# 与KimiAiFree服务交互时用到的token。从ki…...

P6120 [USACO17JAN] Hoof, Paper, Scissor S
难度:普及/提高−; 题意: 石头、剪刀、布游戏,先给出 n n n 轮已经知道的其中一人的对局情况,例如样例: 5 P - 布 P - 布 H - 石头 P - 布 S - 剪刀另外一人,只允许修改一次机会的情况下…...

Android Studio打包APK
1.导出APK安装包 如果是首次打包,Create new 单击蓝色对话框右边文件夹📂图标 ,选择密钥保存路径,然后在下方File name对话框中填写您想要名称,再点击OK回到密钥创建对话框。 在此对话框中填写密码(Passwo…...

08 比特币通用技术介绍
比特币分层 比特币区块结构 存储结构 区块是比特币存储交易的结构,一个区块总是指向其父节点。 一个区块包含三个字段:区块头、区块交易数量、交易列表。交易数量受到区块大小限制,输入、输出数量和脚本都会占用区块空间,矿工往…...

拟合损失函数
文章目录 拟合损失函数一、线性拟合1.1 介绍1.2 代码可视化1.2.1 生成示例数据1.2.2 损失函数1.2.3 绘制三维图像1.2.4 绘制等高线1.2.5 损失函数关于斜率的函数 二、 多变量拟合2.1 介绍2.2 代码可视化2.2.1 生成示例数据2.2.2 损失函数2.2.3 绘制等高线 三、 多项式拟合3.1 介…...

二进制安卓清单 binary AndroidManifest - XCTF apk 逆向-2
XCTF 的 apk 逆向-2 题目 wp,这是一道反编译对抗题。 题目背景 AndroidManifest.xml 在开发时是文本 xml,在编译时会被 aapt 编译打包成为 binary xml。具体的格式可以参考稀土掘金 MindMac 做的类图(2014),下面的博…...

在线免费快速无痕去除照片海报中的文字logo
上期和大家分享了用photoshop快速无痕去除照片海报中的文字logo的方法,有的同学觉得安装PS太麻烦,有那下载安装时间早都日落西山了,问有没有合适的在线方法可以快速去除;达芬奇上网也尝试了几个网站,今天分享一个对国人…...

引领未来科技潮流:Web3 前沿发展趋势
随着技术不断发展,我们正站在一个全新的互联网时代的门槛上,Web3的出现正在重新定义互联网的构架和运作方式。Web3,作为互联网的下一代发展趋势,其核心思想是去中心化、开放与用户主权。与现有的Web2.0相比,Web3更加注…...

【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。 这一节课我们不学习新的知识,我们来做一个扫雷小游戏 目录 扫雷小游戏概述一、扫雷游戏分析…...

08.OSPF 特殊区域及其他特性
OSPF 特殊区域及其他特性 一. 前言OSPF的四个特殊区域Stub末梢区域Totally Stub完全末梢区域NSSATotally NSSA完全的NSSA二.Stub 区域和 Totally Stub 区域(1)网络规模变大引发的问题(2)传输区域和末端区域(3)Stub 区域(4)Totally Stub 区域三.NSSA 区域和 Totally NSS…...

人工智能在医疗领域的应用有哪些?
人工智能在医疗领域的应用十分广泛,涵盖了诊断、治疗、药物研发等多个环节,以下是一些主要的应用: 医疗影像诊断 疾病识别:通过分析 X 光、CT、MRI 等影像,人工智能算法能够识别出肿瘤、结节、骨折等病变,…...

c#使用Confluent.Kafka实现生产者发送消息至kafka(远程连接kafka发送消息超时的解决 Local:Message timed out)
水一篇: 参考:c#使用Confluent.Kafka实现生产者发送消息至kafka(远程连接kafka发送消息超时的解决 Local:Message timed out) - 寒冰之光 - 博客园 该死的Kafka,远程连接Kafka超时以及解决办法 - 博客王大…...

【2025年数学建模美赛F题】(顶刊论文绘图)模型代码+论文
全球网络犯罪与网络安全政策的多维度分析及效能评估 摘要1 Introduction1.1 Problem Background1.2Restatement of the Problem1.3 Literature Review1.4 Our Work 2 Assumptions and Justifications数据完整性与可靠性假设:法律政策独立性假设:人口统计…...

DeepSeek 的背景介绍
在全球人工智能大模型蓬勃发展的浪潮中,DeepSeek 宛如一颗耀眼的新星,迅速崛起并吸引了众多关注的目光。它的出现不仅为人工智能领域注入了新的活力,也在一定程度上改变了行业的竞争格局。 一、创立背景与资金支持 DeepSeek,中文…...

Meta 计划 2025 年投资 650 亿美元推动 AI 发展
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

信息学奥赛一本通 2110:【例5.1】素数环
【题目链接】 ybt 2110:【例5.1】素数环 【题目考点】 1. 深搜回溯 2. 质数 【解题思路】 1~n的数字构成一个环,要求相邻数字加和必须是质数。 该题最终输出的是一个序列,只不过逻辑上序列最后一个数字的下一个数字就是序列的第一个数字…...

Redis、MongoDB 和 MySQL评估
Redis、MongoDB 和 MySQL 是三种不同类型的数据库系统,各自有独特的特点和适用场景。MySQL 是一个关系型数据库管理系统(RDBMS),而 Redis 和 MongoDB 是非关系型数据库(NoSQL)。以下是对这三者的比较以及它…...

P1719 最大加权矩形
为了更好的备战 NOIP2013,电脑组的几个女孩子 LYQ,ZSC,ZHQ 认为,我们不光需要机房,我们还需要运动,于是就决定找校长申请一块电脑组的课余运动场地,听说她们都是电脑组的高手,校长没有马上答应他们…...

在生产环境中部署和管理 Apache:运维从入门到精通
在生产环境中部署和管理 Apache:运维从入门到精通 引言 Apache HTTP Server(简称 Apache)作为世界上最受欢迎的 Web 服务器之一,因其稳定性、灵活性和丰富的模块支持而被广泛使用。从个人网站到企业级应用,Apache 都能游刃有余。然而,要想在生产环境中高效部署和管理 A…...

DeepSeek API 的获取与对话示例
代码文件下载:Code 在线链接:Kaggle | Colab 文章目录 注册并获取API环境依赖设置 API单轮对话多轮对话流式输出更换模型 注册并获取API 访问 https://platform.deepseek.com/sign_in 进行注册并登录: 新用户注册后将赠送 10 块钱余额&#…...

【愚公系列】《循序渐进Vue.js 3.x前端开发实践》027-组件的高级配置和嵌套
标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…...

预测性维护系统:让设备“未卜先知”
预测性维护系统:让设备“未卜先知” 在工业4.0的浪潮中,设备管理正在向智能化转型。传统的设备维护方式,要么是定期维护(时间消耗大),要么是被动维修(问题发生后再处理)。这种方式效率低下且成本高昂。而预测性维护(Predictive Maintenance,简称PdM)则为设备管理提…...

Qt Ribbon使用实例
采用SARibbon创建简单的ribbon界面 实例代码如下所示: 1、头文件: #pragma once #include <SARibbonBar.h> #include "SARibbonMainWindow.h" class QTextEdit; class SAProjectDemo1 : public SARibbonMainWindow { Q_OBJECT pub…...

Midscene.js:重新定义UI自动化的新时代工具
前言 Midscene.js 是一个创新的、面向开发者的 UI 自动化解决方案,并通过人工智能技术简化自动化脚本的编写与维护。 它提供了三种核心方法——交互(.ai, .aiAction)、提取(.aiQuery)和断言(.aiAssert&am…...

【C语言基础】编译并运行第一个C程序
博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 博客内容主要围绕: 5G/6G协议讲解 高级C语言讲解 Rust语言讲解 文章目录 编译并运行第一个C程序一、编译上面的程序二、运行上面的程序…...

处理 .gitignore 未忽略文件夹问题
本地删除缓存 例如 .idea 文件夹被其他同事误提交,那么他本地执行以下代码 git rm -r --cached .idea对应本地再提交即可...

php-phar打包避坑指南2025
有很多php脚本工具都是打包成phar形式,使用起来就很方便,那么如何自己做一个呢?也找了很多文档,也遇到很多坑,这里就来总结一下 phar安装 现在直接装yum php-cli包就有phar文件,很方便 可通过phar help查看…...

卡特兰数学习
1,概念 卡特兰数(英语:Catalan number),又称卡塔兰数,明安图数。是组合数学中一种常出现于各种计数问题中的数列。它在不同的计数问题中频繁出现。 2,公式 卡特兰数的递推公式为:f(…...

第05章 10 地形梯度场模拟显示
在 VTK(Visualization Toolkit)中,可以通过计算地形数据的梯度场,并用箭头或线条来表示梯度方向和大小,从而模拟显示地形梯度场。以下是一个示例代码,展示了如何使用 VTK 和 C 来计算和显示地形数据的梯度场…...