当前位置: 首页 > news >正文

多线程(线程安全)

一、线程安全的风险来源

1.1 后厨的「订单撞单」现象

场景:两服务员同时录入客人点单到同一个菜单本
问题

  1. 订单可能被覆盖
  2. 菜品数量统计错误

Java中的表现

public class OrderServlet extends HttpServlet {private int totalOrders = 0; // 共享计数器protected void doGet(HttpServletRequest req, HttpServletResponse resp) {totalOrders++; // 并发时错误}
}

二、线程安全三要素

2.1 问题的根源机制
问题类型厨房类比解决方案Java对应概念
原子性厨师查看库存到下单三步可能中断步骤打包成一个原子操作synchronized块
可见性更新库存忘记同步到总台中央公告板强制更新状态volatile关键字
有序性备菜与送餐顺序调换确保工序合理顺序避免指令重排

三、核心锁机制synchronized

3.1 后厨传菜窗口的排队系统
public class KitchenService {private final Object lock = new Object();public void processOrder(Order order) {synchronized(lock) { // 类似传菜窗口排队拿号// 处理食材库存和制作工序}}
}

重要特性

  • 重入性:大厨可以多次进出冷藏库(避免自锁)
  • 互斥性:同一时间只有一个窗口可取菜
  • 作用域:锁定单个对象(传菜窗口)或整个餐馆(类锁)

四、内存屏障volatile原理

4.1 实时更新的库存显示屏
class StockManager {public volatile int beefStock = 100; // 各后厨实时可见最新库存public void updateStock(int used) {beefStock -= used; // 注意:volatile无法保证多步原子性!}
}

为什么需要 volatile
new Singleton() 分为三步:

  1. 分配内存
  2. 初始化对象
  3. 将引用指向内存地址
    若没有 volatile,可能 JVM 会将 2 和 3 重排序,导致其他线程拿到未初始化的对象!

解决的问题

.可见性问题(强制刷新内存)
  • 问题表现
    A 线程修改了共享变量的值,但 B 线程看到的值还是旧的(因为 CPU 缓存未同步到主存)。
  • volatile 作用
    当一个线程修改 volatile 变量的值时,其他线程能立即看到最新值,如同直接操作主内存。让所有线程看见最新的自己,且不乱序.

适用场景

  • 菜单状态标记位(如是否停止接单)
  • 配置参数实时更新(动态调整菜品价格)

五、生产-消费协作模型

5.1 前厅与后厨的订单协作
public class Restaurant {private final BlockingQueue<Order> orderQueue = new LinkedBlockingQueue<>(20);// 服务员提交订单(生产者)void submitOrder(Order order) throws InterruptedException {orderQueue.put(order); }// 厨房处理订单(消费者)void processOrders() {while(true) {Order order = orderQueue.take();cook(order);}}
}

优势

  1.  削峰:用餐高峰避免厨师过载
  2. 解耦:前厅接单与厨房制作分离

六、单例模式安全实现

6.1 中央调料柜的双重检查
public class SpiceCabinet {private static volatile SpiceCabinet instance;public static SpiceCabinet getInstance() {if (instance == null) { // 第一次快速校验synchronized(SpiceCabinet.class) {if (instance == null) { // 二次确认instance = new SpiceCabinet();}}}return instance;}
}
第一个if语句(外层检查)

if (instance == null) { // 第一次快速校验

  • 作用:快速路径 (Fast Path)

    • 当实例已经存在时,直接跳过加锁步骤,立即返回实例。
    • 当实例不存在时,才会进入同步块竞争锁。
  • 解决的问题减少锁竞争
    如果去掉这层检查,每次调用getInstance()都必须加锁(无论实例是否已存在),导致即便实例已创建,线程仍需排队竞争锁,性能严重下降。

特点

  • 首次访问可能稍慢(需要加锁初始化)
  • 后续高效获取(无需锁竞争)
第二个if语句(内层检查)

if (instance == null) { // 二次确认

  • 作用:严格保序 (Strict Serialization)

    • 当多个线程同时通过外层检查进入同步块时(初始化阶段),防止多次创建实例。
    • 仅第一个获取锁的线程完成初始化,后续线程发现instance != null后直接返回。

常见误区说明 ✅❌

错误写法问题根源正确方案
去掉外层if每次调用均加锁,性能低下保留外层用于快速路径判断
去掉内层if允许初始化多次(破坏单例)内层确保锁内唯一性
volatile可能导致部分初始化对象被访问volatile禁用指令重排和强制同步

总结

双检锁双if的精髓在于:通过两次检查分别应对不同的并发场景

  • 外层if → 应对已初始化场景(高性能)
  • 内层if → 应对未初始化场景(安全性)

同时,volatile确保了这一机制在极端的并发环境下的最终正确性。

七、线程池管理策略

7.1 后厨人员的弹性调度
ExecutorService kitchenStaff = Executors.newCachedThreadPool(); // 订单到来动态分配厨师
public void handleOrder(Order order) {kitchenStaff.submit(() -> {prepareIngredients(order);cookDish(order);notifyServing(order);});
}

资源优化点

  • 核心厨师(corePoolSize)保持随时待命
  • 临时工(maximumPoolSize)应对就餐高峰
  • 空闲回收(keepAliveTime)降低运营成本

八.故障自排查

现象可能原因检查点
数据不一致未同步共享资源检查++/--操作是否有锁
吞吐量下降过度同步或锁竞争使用JConsole观察锁状态
CPU利用率异常高忙等待或无限制循环检查是否有sleep/等待机制
进程僵死死锁(互相等待资源)检测锁获取顺序是否一致

相关文章:

  • MacOS上如何运行内网穿透详细教程
  • Puter部署指南:基于Docker的多功能个人云平台掌控自己的数据
  • 《Pinia 从入门到精通》Vue 3 官方状态管理 -- 进阶使用篇
  • 音视频之H.265/HEVC量化
  • Streamlit从入门到精通:构建数据应用的利器
  • CGAL 网格等高线计算
  • 参考文献新国标GB/T 7714-2025的 biblatex 实现
  • CF每日4题
  • 云智融合普惠大模型AI,政务服务重构数智化路径
  • openwrt作旁路由时的几个常见问题 openwrt作为旁路由配置zerotier 图文讲解
  • 【数据分析实战】使用 Matplotlib 绘制玫瑰图
  • 【hadoop】HBase分布式数据库安装部署
  • P1217 [USACO1.5] 回文质数 Prime Palindromes【python】
  • Crawl4AI 部署安装及 n8n 调用,实现自动化工作流(保证好使)
  • Kotlin基础知识全面解析(下)
  • 深度解析 Kubernetes 配置管理:如何安全使用 ConfigMap 和 Secret
  • Kotlin Multiplatform--02:项目结构进阶
  • 【产品经理从0到1】Axure介绍
  • 认识游戏循环
  • Flask + ajax上传文件(一)
  • 牛市早报|商务部:目前中美之间未进行任何经贸谈判
  • 海关总署牵头部署开展跨境贸易便利化专项行动
  • 吃饭睡觉打国米,如今的米兰把意大利杯当成宝
  • 百位名人写“茶”字,莫言王蒙贾平凹都写了
  • 联手华为猛攻主流市场,上汽集团总裁:上汽不做生态孤岛
  • 外卖江湖战火重燃,骑手、商家、消费者在“摇摆”什么?