news 2026/7/12 8:40:05

Java并发——共享资源并发风险

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java并发——共享资源并发风险

在多线程编程中,线程之间共享资源是常态。然而,当多个线程同时访问同一块内存区域时,如果没有正确的同步机制,就会引发一系列令人头疼的问题。数据覆盖、原子性破坏、临界资源竞争,这些概念看似抽象,却每天都在并发系统中上演。本文将深入剖析这三种常见的并发风险,通过实例代码揭示其本质,并提供应对策略。

一、共享资源为何成为风险

在单线程环境下,代码执行顺序是确定的,每次运行结果都是一致的。但在多线程环境中,操作系统通过时间片轮转让多个线程“同时”运行,线程的执行顺序变得不可预测。当多个线程同时读写共享变量时,就会出现交错执行,导致数据不一致。

所谓共享资源,就是指多个线程都能访问的变量或对象。例如:

  • 实例变量(堆内存)

  • 静态变量(方法区)

  • 外部资源(文件、数据库连接)

共享资源本身并不危险,危险的是缺乏协调的并发访问。

二、数据覆盖问题:丢失的更新

2.1 问题描述

数据覆盖,也称为“丢失更新”(Lost Update),是指两个线程同时对同一变量进行修改,后一个线程的写入覆盖了前一个线程的结果,导致部分更新丢失。

2.2 典型案例:计数器

假设我们有一个计数器,两个线程各执行100万次自增操作,理论上最终结果应为200万。但实际运行往往小于200万。

public class Counter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } }

启动两个线程,每个线程调用increment()100万次:

Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1_000_000; i++) { counter.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1_000_000; i++) { counter.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(counter.getCount()); // 通常输出小于2_000_000

2.3 原因分析

count++并不是一个原子操作,它在底层分解为三个步骤:

  1. 从内存读取count的值到寄存器。

  2. 在寄存器中执行加1操作。

  3. 将新值写回内存。

当两个线程并发执行时,可能出现如下交错:

线程1: 读取count (0) 线程2: 读取count (0) 线程1: 加1 (得到1) 线程2: 加1 (得到1) 线程1: 写回 (count=1) 线程2: 写回 (count=1)

最终结果只有1,丢失了一次更新。这就是数据覆盖问题。

三、原子性破坏:非原子操作的风险

3.1 什么是原子性

原子性是指一个操作要么全部执行且不受干扰,要么完全不执行。在Java中,对基本类型变量的赋值和读取通常是原子的(long和double例外),但像i++i += 1这样的复合操作不是原子的。

3.2 原子性被破坏的演示

除了i++,任何包含“读-改-写”模式的操作都是非原子的。例如,a = b + c同样涉及读取、计算、赋值三步。当这些步骤之间被其他线程插入时,数据就会出错。

3.3 字节码层面看原子性

使用javap -c Counter查看increment方法的字节码:

public void increment(); Code: 0: aload_0 1: dup 2: getfield #2 // 读取count 5: iconst_1 6: iadd // 加1 7: putfield #2 // 写回count 10: return

可以看到getfield(读)、iadd(加)、putfield(写)三条指令,它们之间没有任何锁保护,因此线程可能在任意时刻被调度出去,导致原子性被破坏。

四、临界资源竞争:超卖与负数票

4.1 问题描述

临界资源(Critical Resource)是指一次只能被一个线程访问的资源。当多个线程同时竞争临界资源且未加保护时,就会出现资源被超额使用或状态异常的情况。最经典的例子就是售票系统

4.2 售票系统演示

假设有100张票,多个窗口(线程)同时卖票:

public class TicketSeller { private int tickets = 100; public void sell() { if (tickets > 0) { // 模拟售票耗时 try { Thread.sleep(10); } catch (InterruptedException e) {} tickets--; System.out.println(Thread.currentThread().getName() + "售出一张,剩余:" + tickets); } else { System.out.println(Thread.currentThread().getName() + "票已售罄"); } } }

启动多个线程同时卖票:

TicketSeller seller = new TicketSeller(); for (int i = 0; i < 20; i++) { new Thread(() -> { for (int j = 0; j < 10; j++) { seller.sell(); } }, "窗口" + i).start(); }

运行结果可能出现:

  • 同一张票被多个线程售出(超卖)

  • 剩余票数出现负数

  • 最终票数不等于初始值

4.3 原因分析

if (tickets > 0)tickets--之间不是原子的。当多个线程同时进入判断时,它们都看到tickets大于0,然后各自执行减操作,导致票数被重复减少,甚至出现负数。

这种竞争导致了临界资源的不安全访问

五、应对策略:让共享资源安全起来

针对上述问题,Java提供了多种同步机制:

5.1 synchronized 关键字

  • 同步方法:锁住当前对象(实例方法)或Class对象(静态方法)。

  • 同步代码块:指定任意对象作为锁,粒度更细。

public synchronized void increment() { // 实例方法锁 count++; } public void sell() { synchronized (this) { if (tickets > 0) { tickets--; } } }

5.2 Lock 接口

提供更灵活的锁操作,如尝试锁、可中断锁、公平锁等。

Lock lock = new ReentrantLock(); lock.lock(); try { // 临界区 } finally { lock.unlock(); }

5.3 原子类

对于简单的计数器,可以使用AtomicInteger等原子类,内部通过CAS(Compare-And-Swap)实现无锁并发。

private AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); // 原子自增

5.4 volatile 与可见性

volatile保证变量的可见性,但不保证原子性。适用于单次读写的标志位。

六、总结

并发编程中的共享资源风险,源于操作系统的时间片轮转和线程交错执行。数据覆盖原子性破坏临界资源竞争是三种最常见的并发问题,它们本质都是对共享资源的多线程访问缺乏协调。

要避免这些风险,必须为临界区提供互斥保护,确保同一时刻只有一个线程能执行关键操作。从synchronizedLock,再到Atomic类,Java提供了丰富的工具,开发者应根据场景选择合适的同步机制,既保证正确性,又兼顾性能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/23 10:39:29

手把手教你用W25Q64 SPI Flash扩展LVGL显示空间(附FatFs移植避坑指南)

手把手教你用W25Q64 SPI Flash扩展LVGL显示空间&#xff08;附FatFs移植避坑指南&#xff09; 在嵌入式GUI开发中&#xff0c;LVGL凭借其轻量级和高度可定制性成为许多开发者的首选。然而&#xff0c;当面对丰富的图形界面需求时&#xff0c;片上Flash的存储空间往往捉襟见肘。…

作者头像 李华
网站建设 2026/3/23 10:37:22

Pyrosetta安装避坑指南:conda镜像源配置与1分钟快速验证法

Pyrosetta安装避坑指南&#xff1a;conda镜像源配置与1分钟快速验证法 生物信息学和计算化学领域的研究者常常需要处理复杂的蛋白质结构预测和分子对接问题。Pyrosetta作为Rosetta套件的Python接口&#xff0c;为这些任务提供了便捷的编程环境。然而&#xff0c;许多初学者在安…

作者头像 李华
网站建设 2026/3/23 10:35:30

丹青识画技术详解:基于达摩院OFA引擎的东方美学视觉理解实现路径

丹青识画技术详解&#xff1a;基于达摩院OFA引擎的东方美学视觉理解实现路径 当一张照片或一幅画作摆在眼前&#xff0c;我们如何让机器像一位饱读诗书的文人墨客那样&#xff0c;不仅看懂画面内容&#xff0c;还能用充满意境的文字和书法艺术将其描绘出来&#xff1f;这正是「…

作者头像 李华
网站建设 2026/3/23 10:23:38

PCA9685驱动详解:12位PWM芯片的嵌入式底层实现与同步控制

1. PCA9685驱动芯片技术解析与嵌入式底层实现指南PCA9685是一款由NXP&#xff08;原Philips Semiconductors&#xff09;推出的专用IC总线LED控制器&#xff0c;其核心定位并非通用GPIO扩展器&#xff0c;而是面向高精度、多通道、低抖动PWM输出的工业级LED调光与伺服控制场景。…

作者头像 李华
网站建设 2026/3/23 10:20:55

洛谷:P1208 [USACO1.3] 混合牛奶 Mixing Milk

题目描述由于乳制品产业利润很低&#xff0c;所以降低原材料&#xff08;牛奶&#xff09;价格就变得十分重要。帮助 Marry 乳业找到最优的牛奶采购方案。Marry 乳业从一些奶农手中采购牛奶&#xff0c;并且每一位奶农为乳制品加工企业提供的价格可能相同。此外&#xff0c;就像…

作者头像 李华
网站建设 2026/3/23 10:18:13

Ollama运行Qwen2.5-VL:7B模型在跨境电商商品图合规检测应用

Ollama运行Qwen2.5-VL&#xff1a;7B模型在跨境电商商品图合规检测应用 1. 引言&#xff1a;跨境电商的“合规之痛” 如果你是做跨境电商的&#xff0c;一定遇到过这样的头疼事&#xff1a;辛辛苦苦拍好商品图&#xff0c;上传到平台&#xff0c;结果因为图片里出现了不该出现…

作者头像 李华