并发编程初学者的噩梦:线程安全与锁机制误区

前言

请添加图片描述

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:https://www.captainbed.cn/z
在这里插入图片描述
请添加图片描述


1. 错误场景复现

场景1:非线程安全的集合操作

// 多线程共享HashMap导致数据错乱
Map<String, Integer> counterMap = new HashMap<>();

// 线程1
new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        counterMap.put("key", counterMap.getOrDefault("key", 0) + 1);
    }
}).start();

// 线程2
new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        counterMap.put("key", counterMap.getOrDefault("key", 0) + 1);
    }
}).start();

// 预期结果:2000,实际结果可能为 1000~2000 之间的任意值

后果:数据不一致、丢失更新,甚至引发ConcurrentModificationException。


场景2:错误使用 synchronized 锁

// 误用锁对象导致同步失效
private Integer lock = 1; // Integer对象不可变,每次赋值创建新对象!

public void unsafeUpdate() {
    synchronized (lock) { // 锁对象变化,同步块失效
        // 业务逻辑
    }
    lock++; // 修改锁对象引用
}

隐患:看似加锁,实际每个线程持有不同的锁对象,完全无同步效果。


场景3:线程池配置灾难

// 创建无界队列线程池
ExecutorService executor = Executors.newCachedThreadPool();

// 提交大量任务
for (int i = 0; i < 1_000_000; i++) {
    executor.submit(() -> {
        // 执行耗时操作
        Thread.sleep(1000);
        return;
    });
}

后果:任务队列无限堆积,最终导致OutOfMemoryError: unable to create new native thread。


2. 原理解析

线程安全的三大核心问题

  1. 竞态条件(Race Condition):多个线程对共享资源的非原子操作导致结果不确定性
  2. 内存可见性:线程本地缓存与主内存不一致(需volatile或锁保证可见性)
  3. 指令重排序:编译器/CPU优化打乱执行顺序(happens-before规则约束)

synchronized 的底层实现

// synchronized 字节码示例
monitorenter  // 进入锁(依赖对象头的Mark Word)
// 临界区代码
monitorexit   // 释放锁
  • 锁升级机制:无锁 → 偏向锁 → 轻量级锁(CAS自旋) → 重量级锁(OS互斥量)
  • 锁粗化/消除:JVM优化策略(合并相邻锁、去除不可能竞争的锁)

线程池参数陷阱

参数错误配置正确实践
corePoolSize设为0根据CPU核心数设置(N+1)
workQueue使用无界队列(如LinkedBlockingQueue)使用有界队列 + 拒绝策略
keepAliveTime设置过大(如1小时)根据任务特性设置(通常60秒)
rejectedHandler忽略(默认AbortPolicy)自定义降级策略(如记录日志后丢弃)

3. 正确解决方案

方案1:使用JUC并发集合

// 替换HashMap为ConcurrentHashMap
Map<String, Integer> safeMap = new ConcurrentHashMap<>();

// 原子更新操作
safeMap.compute("key", (k, v) -> (v == null) ? 1 : v + 1);

// 或使用LongAdder(更高性能的计数器)
LongAdder adder = new LongAdder();
adder.increment();
选型指南:
  • ConcurrentHashMap:高并发读场景
  • CopyOnWriteArrayList:读多写少场景
  • BlockingQueue:生产者-消费者模式

方案2:显式锁与原子类

// 使用ReentrantLock替代synchronized
private final Lock lock = new ReentrantLock();

public void safeUpdate() {
    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock(); // 确保解锁
    }
}

// 使用AtomicInteger实现无锁化
private AtomicInteger counter = new AtomicInteger(0);

public void increment() {
    counter.incrementAndGet(); // CAS操作
}
优势:
  • 更灵活的锁机制(可中断、超时、公平锁)
  • 避免锁粗化带来的性能损耗

方案3:线程池标准化创建

// 手动创建线程池(避免Executors陷阱)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4, // corePoolSize
    8, // maxPoolSize
    60, // keepAliveTime(秒)
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1000), // 有界队列
    new ThreadFactoryBuilder().setNameFormat("worker-%d").build(),
    new CustomRejectPolicy() // 自定义拒绝策略
);

// 自定义拒绝策略(记录日志后丢弃)
static class CustomRejectPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        log.warn("任务被拒绝:{}", r.toString());
    }
}

4. 工具与最佳实践

并发调试工具

  1. jstack:
    jstack <pid> > thread_dump.txt  # 分析线程状态与锁持有情况
    
  2. Arthas:
    watch com.example.Service * '{params, throwExp}' -x 3  # 动态监控方法调用
    

代码规范建议

  • 锁范围最小化:只在必要代码块加锁
  • 避免嵌套锁:预防死锁(按固定顺序获取锁)
  • 优先使用并发工具类:如CountDownLatch、CyclicBarrier、Semaphore
  • 禁止在锁内调用外部方法:防止未知阻塞

5. Code Review检查清单

检查项正确做法
是否使用线程安全集合?用ConcurrentHashMap代替HashMap
锁对象是否被意外修改?用final修饰锁对象(如private final Object lock = new Object())
线程池是否配置拒绝策略?禁止使用默认的AbortPolicy(引发RejectedExecutionException)
是否误用volatile?volatile仅保证可见性,不保证原子性

6. 真实案例

某交易系统使用HashMap缓存最新价格:

private static Map<String, BigDecimal> priceCache = new HashMap<>();

// 多线程更新缓存
public void updatePrice(String symbol, BigDecimal price) {
    priceCache.put(symbol, price);
}

// 多线程读取缓存
public BigDecimal getPrice(String symbol) {
    return priceCache.get(symbol);
}

后果:

  • 部分线程读取到null值,导致交易异常
  • 高峰时段触发ConcurrentModificationException,服务不可用

修复方案:

  1. 替换为ConcurrentHashMap
  2. 使用computeIfAbsent原子化初始化操作
  3. 增加缓存空值(Null Object模式)

总结

  • 线程安全是设计出来的:而非通过加锁硬编码
  • 锁是性能敌人:无锁算法 > 细粒度锁 > 粗粒度锁
  • 工具决定效率:JUC并发包 + 监控工具 = 高可用基石
  • 池化资源需谨慎:参数调优与拒绝策略缺一不可

下期预告:《浅拷贝与深拷贝:对象复制的隐藏风险》——从内存泄漏到数据篡改,全面解析对象复制的正确姿势。

联系作者

职场经验分享,Java面试,简历修改,求职辅导尽在科技泡泡
思维导图面试视频合集
在这里插入图片描述
在这里插入图片描述

Apollo智能驾驶|Apollo1.0到7.0版本快速了解 Apollo官网:https://apollo.auto/先来看下Apollo各版本发布图:下面对各个版本做下介绍。Apollo 1.0: 循迹功能实现Apollo 1.0(也称为自动GPS航点跟踪)可在封闭的场地(例如测试跑道或停车场)中工作。必须进行此安装,以确保Apollo与您的车辆完美配合。下图列出了Apollo 1.0中的各个模块。Apollo 1.5: 巡航功能Apollo 1.5适用于固定车道巡航。 阅读详情

相关推荐

什么是Apollo自动驾驶平台?

阿波罗(Apollo)是百度发布的面向汽车行业及自动驾驶领域的合作伙伴提供的软件平台。发布时间是2017年4月19日,旨在向汽车行业及自动驾驶领域的合作伙伴提供一个开放、完整、安全的软件平台,帮助他们结合车辆和硬件系统,快速搭建一套属于自己的完整的自动驾驶系统。而将这个计划命名为“Apollo”计划,就是借用了阿波罗登月计划的含义。CES 2018 百度Apollo 2.0无人车美国桑尼维尔试乘。

征服Bug的博客 2万+

PostgreSQL 高并发优化:从 “连接数满到崩” 到 “支撑 10 万 QPS” 的实战技巧

PostgreSQL高并发优化实战:从MySQL无缝迁移的终极指南 本文针对MySQL高并发场景的三大痛点(连接数满、锁等待超时、QPS瓶颈),系统性地介绍了PostgreSQL的高速并发解决方案。通过四大核心优化模块(连接池配置、锁机制优化、参数调优、SQL优化),结合电商秒杀场景案例,详细展示了PG如何实现单实例10万QPS的突破性性能。文章特别采用MySQL与PG16的对比方式,帮助MySQL开发者快速理解PG的高并发优势:轻量级线程模型(内存占用仅为MySQL1/10)、精细锁机制(锁等待降低80%

lamehsl的专栏 1138

自旋锁用错了地方?单核 vs. 多核场景下的锁选择避坑指南

本文深入探讨了自旋锁在单核与多核系统中的适用场景与性能差异,提供了锁选择的实用决策框架。通过分析自旋锁的工作原理、单核系统的潜在问题以及多核环境下的优势,帮助开发者避免常见陷阱,优化并发编程性能。

weixin_31421613的博客 351

Redis线程模型深度解析:从单线程到多线程I/O的演进与设计哲学

I/O多路复用是高性能网络编程的核心技术,它允许单个线程高效管理大量并发连接,通过事件驱动模型避免了传统阻塞I/O的资源浪费。其原理基于操作系统提供的epoll、kqueue等系统调用,实现高并发下的低延迟响应。这一技术在高性能服务器设计中具有重要价值,尤其适用于需要处理海量短连接的场景,如缓存、消息队列和实时通信服务。Redis正是这一技术的典范应用,它通过单线程命令执行保证数据操作的原子性,同时利用I/O多路复用处理网络并发。随着Redis 6.0引入多线程I/O,网络数据的读写和协议解析被并行化,但核

weixin_34302798的博客 396

Python多线程端口扫描与Socket通信实战:从原理到安全工具开发

网络编程是计算机通信的基础,其核心在于通过套接字(Socket)实现不同主机间的数据交换。Socket作为操作系统提供的抽象接口,封装了TCP/IP协议栈的底层细节,允许开发者以流或数据报的形式进行网络通信。理解Socket的工作原理,特别是TCP三次握手和连接状态管理,是构建稳定网络应用的基石。在安全工程和自动化运维领域,端口扫描作为网络探测的基础技术,通过尝试与目标主机的特定端口建立连接,来发现开放的服务和潜在的攻击面。传统单线程扫描效率低下,而多线程技术通过并发执行I/O操作,能极大提升扫描速度,这正

weixin_33827590的博客 323

Apollo自动驾驶源码

百度Apollo自动驾驶演示版本源码。 安装步骤: 运行如下命令启动docker的release环境: bash docker/scripts/release_start.sh 运行如下命令进入docker的release环境: bash docker/scripts/release_into.sh 运行如下命令回放位rosbag: python docs/demo_guide/rosbag_helper.py demo_1.5.bag # 下载rosbag rosbag play demo_1.5.bag --loop 选项 --loop 用于设置循环回放模式. 打开Chrome浏览器,在地址栏输入localhost:8888即可访问Apollo Dreamview

自动驾驶纵向控制算法

这篇论文讲述了智能驾驶方向纵向控制算法,侧重与卡车类的纵向算法

从碰撞电离到雪崩击穿:Sentaurus仿真中的模型选择与验证艺术

本文深入探讨了Sentaurus TCAD中雪崩击穿模型的选取与验证方法,特别关注平行平面结的仿真实践。通过对比Chynoweth、van Overstraeten等主流模型的适用场景与精度差异,结合具体案例展示了从几何建模到边界条件设定的完整验证流程,并针对非理想因素提供了有效的补偿技术,助力工程师提升器件仿真的准确性与可靠性。

weixin_29074295的博客 592

吃透Chisel语言.02.Chisel+VS Code+Ubuntu/WSL2开发环境搭建与使用

既然是学Chisel语言,那肯定得先把环境配置好,磨刀不误砍柴工嘛。而且有个测试环境的话,在后期的学习过程中自己去试运行代码段可以加深学习效果。这里推荐使用Ubuntu或者基于WSL2(Windows Subsystem Linux 2)的Ubuntu,本人使用的是WSL2上的Ubuntu 20.04.3 LTS,在使用体验上与Ubuntu几乎没有区别,且能同时使用Windows提供的诸多便利。...

github-3rr0r的博客 8448

单片机按键识别篇---单击---双击----长按

  最近做一任务需要使用一个按键实现三种功能,分别是按键单击功能,按键双击功能和按键长按功能,可能之前没有接触过这类按键复用情况,顶多也只是简单识别单击和长按,没有想过双击以上按键功能,也是绞尽脑汁,想了半天,又上网看了些例程,算是对于按键的识别有点小小的了解,感觉这个功能挺实用,在此做个随笔。 一、思路   1.识别长按操作思路:我们使用系统定时器定时,然后让系统每过一段时间去扫描按键I/O...

weixin_30311605的博客 3660

STM32异常与中断过程详解

1. 异常与中断概念引入 异常主要是指来自CPU内部的意外事件,比如执行了未定义指令、算术溢出、除零运算等发生在CPU内部的意外事件,这些异常的发生,会引起CPU运行相应的异常处理程序;中断一般来自硬件(如片上外设、外部I/O输入等)发生的事件,当这些硬件产生中断信号时,CPU会暂停当前运行的程序,转而去处理相关硬件的中断服务程序。但无论是异常还是中断,都会引起程序执行偏离正常的流程,转而去执行异常/中断的处理函数。 下面对异常和中断的介绍,如果中断信号的产生原因来自CPU内部,则称之为异常;如果中断信号来

luobeihai的博客 1万+

华为OD机试双机位C卷-用户入网定期复评(C/C++/Py/Java/Js/Go)

华为OD机试(机考)双机位C卷 【用户入网定期复评】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题【用户入网定期复评】。

qq_45776114的博客 841

Sigrity System SI SerialLink模式进行SFP+协议仿真分析-PCB操作指导

Random(Rj):随机抖动,该抖动用高斯概率分布来描述,通常是由热噪声和其它随机噪声引起的,默认为1%UI RMS(平均值),即和标准值的偏差为1%UI。Random(Rj):随机抖动,该抖动用高斯概率分布来描述,通常是由热噪声和其它随机噪声引起的,默认为1%UI RMS(平均值),即和标准值的偏差为1%UI。Random(Rj):随机噪声,是由信号电压的随机波动引起的,默认为1mv RMS(平均值),即和标准值的偏差为1mv。

weixin_54787054的博客 508

[SVA]SystemVerilog Assertion Property开发技巧总结

SystemVerilog Assertion开发技巧总结 一、在property中可以使用if-else条件语句 如下面的例子,根据misc_phy_12g_rx的取值,选择不同的判断语句 property ck1_en_rose_chk(); @(posedge clk) disable iff(~rst_n) ad_pn_ck1en & (current_state !== ST_IDLE) |->...

元直的博客 5924

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

上一篇: 技术债累积警告:复制粘贴代码的连锁反应
下一篇: 浅拷贝与深拷贝:对象复制的隐藏风险
雪碧有白泡泡
雪碧有白泡泡 优质创作者: 编程框架技术领域 优质创作者: 编程框架技术领域
博客等级 码龄5年 1万+粉丝 44原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪碧有白泡泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值