Java 性能优化实战(三):并发编程的 4 个优化维度

在多核CPU时代,并发编程是提升Java应用性能的关键手段,但不合理的并发设计反而会导致性能下降、死锁等问题。本文将聚焦并发编程的四个核心优化方向,通过真实案例和代码对比,带你掌握既能提升性能又能保证线程安全的实战技巧。

一、线程池参数调优:找到并发与资源的平衡点

线程池是并发编程的基础组件,但参数设置不当会导致线程上下文切换频繁、资源耗尽等问题。合理配置线程池参数能最大化利用CPU和IO资源。

线程池核心参数解析

线程池的核心参数决定了其工作特性:

  • 核心线程数(corePoolSize):保持运行的最小线程数
  • 最大线程数(maximumPoolSize):允许创建的最大线程数
  • 队列容量(workQueue):用于保存待执行任务的阻塞队列
  • 拒绝策略(handler):任务队列满时的处理策略

案例:线程池参数不合理导致的性能坍塌

某API网关系统使用线程池处理下游服务调用,压测时发现TPS上不去,CPU使用率却高达90%。

问题配置

// 错误配置:线程数过多,队列无界
ExecutorService executor = new ThreadPoolExecutor(10,              // corePoolSize1000,            // maximumPoolSize(过大)60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>()  // 无界队列
);

问题分析

  • 最大线程数设置为1000,远超CPU核心数(16核),导致线程上下文切换频繁
  • 无界队列导致任务无限制堆积,内存占用持续增长
  • 线程过多导致CPU大部分时间用于切换线程,而非执行任务

优化配置

// 根据业务场景调整参数
int cpuCore = Runtime.getRuntime().availableProcessors();
ExecutorService executor = new ThreadPoolExecutor(cpuCore * 2,         // corePoolSize:IO密集型任务设为CPU核心数2倍cpuCore * 4,         // maximumPoolSize:控制在合理范围60L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000),  // 有界队列,控制任务堆积new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略:让调用者处理
);

优化效果

  • 线程数控制在64以内,上下文切换减少60%
  • CPU使用率从90%降至60%,但TPS提升了3倍
  • 内存使用趋于稳定,避免了OOM风险

线程池参数配置原则

  1. CPU密集型任务(如数据计算):

    • 核心线程数 = CPU核心数 + 1
    • 队列使用ArrayBlockingQueue,容量适中
  2. IO密集型任务(如网络请求、数据库操作):

    • 核心线程数 = CPU核心数 * 2
    • 可适当增大最大线程数和队列容量
  3. 队列选择

    • 优先使用有界队列(如ArrayBlockingQueue),避免内存溢出
    • 任务优先级高时用PriorityBlockingQueue
  4. 拒绝策略

    • 核心服务用CallerRunsPolicy(牺牲部分性能保证任务不丢失)
    • 非核心服务用DiscardOldestPolicy或自定义策略

二、CompletableFuture:异步编程的性能利器

传统的线程池+Future模式在处理多任务依赖时代码繁琐且效率低下,CompletableFuture提供了更灵活的异步编程模型,能显著提升并发任务处理效率。

CompletableFuture核心优势

  • 支持链式调用和任务组合
  • 提供丰富的异步回调方法
  • 可自定义线程池,避免使用公共线程池带来的干扰

案例:订单查询接口的异步优化

某电商订单详情接口需要查询订单信息、用户信息、商品信息和物流信息,传统串行调用耗时过长。

串行实现(性能差)

// 串行调用,总耗时 = 各步骤耗时之和
public OrderDetail getOrderDetail(Long orderId) {Order order = orderService.getById(orderId);          // 50msUser user = userService.getById(order.getUserId());  // 40msList<Product> products = productService.listByIds(order.getProductIds());  // 60msLogistics logistics = logisticsService.getByOrderId(orderId);  // 70msreturn new OrderDetail(order, user, products, logistics);
}
// 总耗时:约50+40+60+70=220ms

CompletableFuture并行实现

// 自定义线程池,避免使用ForkJoinPool.commonPool()
private ExecutorService orderExecutor = new ThreadPoolExecutor(8, 16, 60L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100),new ThreadFactory() {private final AtomicInteger counter = new AtomicInteger();@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);thread.setName("order-detail-pool-" + counter.incrementAndGet());thread.setDaemon(true);return thread;}}
);public OrderDetail getOrderDetail(Long orderId) {try {// 1. 并行执行四个查询CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> orderService.getById(orderId), orderExecutor);CompletableFuture<User> userFuture = orderFuture.thenComposeAsync(order -> CompletableFuture.supplyAsync(() -> userService.getById(order.getUserId()), orderExecutor), orderExecutor);CompletableFuture<List<Product>> productFuture = orderFuture.thenComposeAsync(order -> CompletableFuture.supplyAsync(() -> productService.listByIds(order.getProductIds()), orderExecutor), orderExecutor);CompletableFuture<Logistics> logisticsFuture = CompletableFuture.supplyAsync(() -> logisticsService.getByOrderId(orderId), orderExecutor);// 2. 等待所有任务完成CompletableFuture.allOf(orderFuture, userFuture, productFuture, logisticsFuture).join();// 3. 组装结果return new OrderDetail(orderFuture.get(),userFuture.get(),productFuture.get(),logisticsFuture.get());} catch (Exception e) {throw new ServiceException("查询订单详情失败", e);}
}
// 总耗时:约max(50,40,60,70)=70ms(并行执行)

优化效果

  • 接口响应时间从220ms降至70ms,性能提升68%
  • 系统吞吐量从500 TPS提升至1500 TPS
  • 资源占用更合理,避免了串行执行时的资源浪费

CompletableFuture实战技巧

  1. 避免使用默认线程池:通过thenApplyAsyncsupplyAsync的第二个参数指定自定义线程池
  2. 异常处理:使用exceptionally()handle()方法处理异步任务异常
  3. 任务组合
    • thenCompose():串联依赖任务
    • thenCombine():合并两个独立任务结果
    • allOf():等待所有任务完成
    • anyOf():等待任一任务完成

三、减少锁粒度:从"大锁"到"小锁"的性能飞跃

锁是保证线程安全的重要手段,但过大的锁粒度会导致线程阻塞严重,通过减小锁粒度能显著提升并发性能。

锁粒度优化思路

  • 将全局锁拆分为多个局部锁
  • 对数据分片加锁,只锁定操作的数据片段
  • 利用并发数据结构(如ConcurrentHashMap)替代手动加锁

案例:库存扣减的锁粒度优化

某秒杀系统的库存扣减操作使用全局锁控制,导致并发抢购时大量线程阻塞。

全局锁实现(性能瓶颈)

// 全局锁导致所有商品的库存操作都需要排队
public class InventoryService {private final Object lock = new Object();private Map<Long, Integer> inventoryMap = new HashMap<>();  // 商品ID -> 库存数量// 全局锁:任何商品的扣减都需要获取同一把锁public boolean deduct(Long productId, int quantity) {synchronized (lock) {Integer stock = inventoryMap.get(productId);if (stock != null && stock >= quantity) {inventoryMap.put(productId, stock - quantity);return true;}return false;}}
}

分段锁优化

public class InventoryService {// 1. 分16个段,降低锁竞争private static final int SEGMENT_COUNT = 16;private final Segment[] segments = new Segment[SEGMENT_COUNT];private final Map<Long, Integer> inventoryMap = new ConcurrentHashMap<>();// 2. 每个段持有自己的锁private static class Segment {final Object lock = new Object();}public InventoryService() {for (int i = 0; i < SEGMENT_COUNT; i++) {segments[i] = new Segment();}}// 3. 根据商品ID路由到不同的段,只锁定对应段public boolean deduct(Long productId, int quantity) {// 计算路由到哪个段int segmentIndex = (int) (productId % SEGMENT_COUNT);Segment segment = segments[segmentIndex];// 只锁定当前段,其他商品的操作不受影响synchronized (segment.lock) {Integer stock = inventoryMap.get(productId);if (stock != null && stock >= quantity) {inventoryMap.put(productId, stock - quantity);return true;}return false;}}
}

进一步优化:使用ConcurrentHashMap

public class InventoryService {// 利用ConcurrentHashMap的分段锁机制private final ConcurrentHashMap<Long, Integer> inventoryMap = new ConcurrentHashMap<>();public boolean deduct(Long productId, int quantity) {// 循环重试机制处理并发更新while (true) {Integer currentStock = inventoryMap.get(productId);if (currentStock == null || currentStock < quantity) {return false;}// CAS机制更新库存,避免显式加锁if (inventoryMap.replace(productId, currentStock, currentStock - quantity)) {return true;}// 更新失败则重试}}
}

优化效果

  • 库存扣减接口的并发能力从500 QPS提升至5000 QPS
  • 锁等待时间从平均80ms降至5ms
  • 系统能稳定支撑秒杀场景的流量峰值

锁优化的其他策略

  1. 锁消除:JVM会自动消除不可能存在共享资源竞争的锁
  2. 锁粗化:将连续的细粒度锁合并为一个粗粒度锁,减少锁开销
  3. 读写分离锁:使用ReentrantReadWriteLock,允许多个读操作并发执行
  4. 无锁编程:使用Atomic系列类、CAS操作替代锁

四、volatile与ThreadLocal:轻量级并发工具的正确使用

volatile和ThreadLocal是Java提供的轻量级并发工具,合理使用能在保证线程安全的同时避免锁带来的性能开销。

volatile:保证内存可见性的轻量级方案

volatile关键字能保证变量的内存可见性,但不能保证原子性,适用于状态标记等场景。

正确使用场景

public class TaskRunner {// 用volatile保证stopFlag的可见性private volatile boolean stopFlag = false;public void start() {new Thread(() -> {while (!stopFlag) {  // 读取volatile变量executeTask();}System.out.println("任务线程已停止");}).start();}// 其他线程调用此方法设置停止标记public void stop() {stopFlag = true;  // 写入volatile变量}private void executeTask() {// 执行任务...}
}

常见误区:试图用volatile保证原子性

// 错误示例:volatile不能保证原子性
public class Counter {private volatile int count = 0;// 多线程调用时会出现计数错误public void increment() {count++;  // 非原子操作,包含读-改-写三个步骤}
}

ThreadLocal:线程私有变量的安全管理

ThreadLocal用于创建线程私有变量,避免多线程共享变量带来的并发问题,特别适合上下文传递场景。

正确使用示例

public class UserContext {// 定义ThreadLocal存储用户上下文private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();// 设置当前线程的用户上下文public static void setUser(User user) {userThreadLocal.set(user);}// 获取当前线程的用户上下文public static User getUser() {return userThreadLocal.get();}// 移除当前线程的用户上下文,避免内存泄漏public static void removeUser() {userThreadLocal.remove();}
}// 使用场景:在拦截器中设置用户上下文
public class AuthInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {User user = authenticate(request);  // 认证用户UserContext.setUser(user);  // 设置到ThreadLocalreturn true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {UserContext.removeUser();  // 务必移除,避免内存泄漏}
}// 业务代码中获取用户上下文
public class OrderService {public void createOrder() {User currentUser = UserContext.getUser();  // 无需参数传递,直接获取// 创建订单逻辑...}
}

ThreadLocal使用注意事项

  1. 必须移除:在任务结束或请求完成时调用remove(),避免线程池场景下的内存泄漏
  2. 避免存储大对象:ThreadLocal中的对象会随线程生命周期存在,大对象会占用过多内存
  3. 谨慎使用InheritableThreadLocal:它会传递给子线程,但可能导致意外的数据共享

并发编程优化的核心原则

并发编程优化的目标是在保证线程安全的前提下最大化系统吞吐量,核心原则包括:

  1. 最小化同步范围:只对必要的代码块加锁,减少线程阻塞时间
  2. 优先使用无锁方案:Atomic系列、ConcurrentHashMap等并发工具性能优于显式锁
  3. 合理控制并发度:线程数并非越多越好,需根据CPU核心数和任务类型调整
  4. 避免线程饥饿:保证锁的公平性或使用tryLock()避免长时间等待
  5. 完善监控告警:通过JMX或APM工具监控线程状态、锁竞争情况

记住:最好的并发设计是让线程尽可能少地进行通信和同步,通过合理的任务拆分和数据隔离,实现"无锁并发"的理想状态。在实际开发中,需结合业务场景选择合适的并发工具,通过压测验证优化效果,才能真正发挥并发编程的性能优势。

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

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

相关文章

【秋招笔试】2025.08.19百度秋招机考第一套

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围在线刷题 bishipass.com 题目一:花园路径优化问题 1️⃣:使用栈维护必须保留的观景点,基于三角不等式判断 2️⃣:贪心策略,检查中间点是否为"转折点" 3️⃣:时间复杂度 …

SmartX 用户建云实践|某人寿保险:从开发测试、核心生产到信创转型,按需推进企业云建设

某人寿保险自 2018 年起开始探索基于 SmartX 超融合架构搭建私有云 IaaS 资源池&#xff0c;先后部署了开发测试业务、生产业务和重要生产业务的 Oracle 数据库&#xff08;含 RAC&#xff09;&#xff0c;并探索了基于海光芯片的信创云搭建&#xff0c;最终以基于超融合架构的…

通道注意力机制|Channel Attention Neural Network

一、通道注意力机制 论文&#xff1a;ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks 近年来&#xff0c;通道注意力机制在提高深度卷积神经网络CNN的性能方面显示出了巨大潜力。然而&#xff0c;大多数现有方法致力于开发更复杂的注意力模块&a…

构建包含IK插件(中文分词插件)的Elasticsearch镜像

#!/bin/bash# 定义变量 ES_VERSION"8.15.3" IMAGE_NAME"elasticsearch-with-ik:${ES_VERSION}" IK_PLUGIN_DIR"./elasticsearch-analysis-ik-${ES_VERSION}" DOCKERFILE_NAME"Dockerfile.es-ik"# 检查IK插件目录是否存在 if [ ! -d &q…

Linux虚拟机安装FTP

文章目录深入理解FTP&#xff1a;从原理到实战配置&#xff08;以VSFTP为例&#xff09;一、FTP基础&#xff1a;你需要知道的核心概念1.1 什么是FTP&#xff1f;1.2 FTP的“双端口”机制1.3 为什么选择VSFTP&#xff1f;二、FTP的两种工作模式&#xff1a;主动与被动2.1 主动模…

开源版CRM客户关系管理系统源码包+搭建部署教程

在数字化转型的浪潮下&#xff0c;客户关系管理&#xff08;CRM&#xff09;成为企业提升竞争力的关键工具。为满足开发者和企业对个性化 CRM 系统的需求&#xff0c;分享一款开源版 CRM 客户关系管理系统&#xff0c;其源码涵盖前台、后台及 Uniapp 源代码&#xff0c;支持快速…

基于“R语言+遥感“水环境综合评价方法技术应用——水线提取、水深提取、水温提、水质提取、水环境遥感等

一&#xff1a;R语言1.1 R语言特点&#xff08;R语言&#xff09;1.2 安装R&#xff08;R语言&#xff09;1.3 安装RStudio&#xff08;R语言&#xff09;&#xff08;1&#xff09;下载地址&#xff08;2&#xff09;安装步骤&#xff08;3&#xff09;软件配置1.4 第一个程序…

MCP 与 Function Calling 打开真实世界的两种“母体”方式

AI Agent的互动之言&#xff1a;当人工智能需要获取实时信息或与外部环境进行交互时&#xff0c;它依赖于特定的技术机制来实现。本文将以通俗易懂的方式&#xff0c;深入解析MCP&#xff08;模型调用协议&#xff09;与函数调用的核心概念&#xff0c;比较二者的异同&#xff…

Ansys Motor-CAD:概述(EMag、THERM、LAB、MECH)

你好&#xff0c;在这篇博客中&#xff0c;我概述了如何使用 Ansys Motor-CAD 模型、模拟、分析和后处理结果来评估电机性能&#xff0c;并帮助您为您的应用选择优化的电机&#xff0c;并通过电机设计选择实现成本效益和效率。我介绍了各种可用的电机类型、可供选择的物理模块和…

AI + 金融领域 + 落地典型案例

目录 一、美国银行智能客服与风控体系 &#xff1a; 1. 推出了虚拟助手 Erica&#xff0c; 2. 构建了先进的风险评估模型&#xff0c; 二、财跃星辰与国泰海通、上海银行合作项目&#xff1a; 1. 投教 AI 助手、投顾 AI 助手、托管 AI 助手 2. AI 手机银行&#xff0c;对…

项目管理进阶——研发项目组织管理制度

第一条 目的 为规范企业的新技术研发、技术创新工作,加强企业项目开发和技术创新能力,应用高新技术提高企业的整体市场竞争力和经济效益,实施公司“科技兴企”的重要决策,根据公司具体情况,特制定本办法。 第二条 范围 本办法适用于以增强自主创新能力和促进企业高新技…

深度学习:入门简介

深度学习&#xff08;Deep Learning, DL&#xff09;是机器学习&#xff08;Machine Learning, ML&#xff09;的一个重要分支&#xff0c;核心是通过模拟人类大脑神经元的连接方式&#xff0c;构建多层神经网络来自动学习数据中的特征和规律&#xff0c;最终实现预测、分类、生…

switch摇杆JoyCon摇杆研究,碳膜摇杆、霍尔电磁摇杆

https://blog.csdn.net/qq_28145393/article/details/125769568 https://zhuanlan.zhihu.com/p/1925522678263056352 插件DIP 碳膜摇杆 6脚&#xff0c;内部两个滑动变阻器&#xff0c;1个按键。 引脚定义如下&#xff1a;1脚AD1、2脚按键GND、3脚按键、4脚AD2、5脚变阻器GND、…

保护 PDF 格式:禁止转换为其他格式文件

在日常办公中&#xff0c;PDF是很常见的文件格式。有时候为了方便编辑&#xff0c;我们会将PDF转换成其他格式文件&#xff0c;比如Word、PPT等&#xff1b;但有时候出于安全考虑&#xff0c;我们又不希望PDF可以随意转换成其他格式文件。那如何禁止转换格式呢&#xff1f;其实…

docker 打包

目录 构建docker容器 使用 Dockerfile 构建自定义镜像 构建docker容器 docker images docker pull pytorch/torchserve:latest-gpu docker imagesdocker run -d --rm --gpus all --name torchserve-dev-bg -u $(id -u):$(id -g) -v /nas:/nas pytorch/torchserve:latest /bi…

云原生俱乐部-k8s知识点归纳(7)

计划是再更两篇就完结k8s系列&#xff0c;其中CRD客户端资源定义会单独列一篇&#xff0c;或许会讲一讲operator。不过当前的k8s并没有细讲operator&#xff0c;因为涉及到很多的go语言内容&#xff0c;以及相关的package的方法。这一部分主要就是讲一讲k8s如何进行监控和升级&…

c语言之进程函数

1. 进程创建#include <sys/types.h>#include <unistd.h>pid_t fork(void);fork 创建一个新进程fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is refe…

学习python第12天

今日任务&#xff1a;DataFrameDataFrame的构造pandas.DataFrame(dataNone, indexNone, columnsNone, dtypeNone, copyFalse)参数说明&#xff1a;data&#xff1a;DataFrame 的数据部分&#xff0c;可以是字典、二维数组、Series、DataFrame 或其他可转换为 DataFrame 的对象。…

C++显示类型转换运算符static_cast使用指南

这是一篇关于 static_cast 用法的文章。本文会从基础概念到常见应用场景全覆盖&#xff0c;并附上代码示例以方便理解。C 中的 static_cast 用法详解 在 C 中&#xff0c;static_cast 是一种显式类型转换运算符&#xff0c;主要用于在编译期进行类型安全的转换。相比 C 风格的强…

es6常用方法来解决功能需求

前言&#xff1a;es6常用方法来解决功能需求。1、出现复杂的json字符串如何去解析&#xff1f;比如&#xff1a;下面这个字符串&#xff0c;如果用json.parse解析发现还是个字符串"\"[{\\\"orgId\\\":\\\"1054021138280960\\\",\\\"orgName…