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

并发编程的问题与管程

1. 安全性、活跃性以及性能问题

分类

问题类型

典型表现

解决策略

安全性

数据竞争、竞态条件

结果不一致、错误

使用互斥锁(synchronized、ReentrantLock)等

活跃性

死锁、活锁、饥饿

程序卡住、不前进

公平锁、随机等待、资源调度优化

性能

串行比例过高

吞吐下降、响应变慢

减少锁持有时间、无锁并发设计

1.1. 安全性问题(Correctness)

定义:程序执行结果符合预期,不出现数据错误或逻辑异常。
本质:线程安全,即多个线程并发访问共享数据时,程序依然表现得“正确”。

原因分析:

安全性问题的根本来源是:

  • 原子性问题:操作不可被中断
  • 可见性问题:一个线程对共享变量的修改,另一个线程看不到
  • 有序性问题:指令执行顺序与预期不一致

典型场景:

  • 存在共享变量变量状态可变(多个线程读写)
  • 存在数据竞争(Data Race):多线程访问共享变量时没有同步机制
  • 存在竞态条件(Race Condition):程序执行结果依赖线程执行顺序,如果顺序不同则结果不同。必须避免。

例子:

synchronized 不能解决所有问题

即使使用了 synchronized 包裹 get() 和 set() 方法,也无法保证 set(get()+1) 的原子性。

synchronized long get() { return count; }

synchronized void set(long v) { count = v; }

void add10K() {

int idx = 0;

while(idx++ < 10000) {

set(get()+1)

}

两个线程都可能读取 count=0,各自加 1 后设置为 1,导致结果不是 2 而是 1。

  • add10K() 方法并不是线程安全的。

解决方案:

那面对数据竞争和竞态条件问题,又该如何保证线程的安全性呢?其实这两类问题,都可以用互斥这个技术方案,而实现互斥的方案有很多,CPU 提供了相关的互斥指令,操作系统、编程语言也会提供相关的 API。从逻辑上来看,我们可以统一归为:

1.2. 活跃性问题(Liveness)(三种锁类型)

定义:程序在并发环境中“能否继续运行下去”。

常见问题类型:

  1. 死锁(Deadlock):多个线程互相等待对方释放资源,永久阻塞。
  2. 活锁(Livelock):线程不断地更换状态/“谦让”,但依然无法前进。
  3. 饥饿(Starvation):线程长期无法获得资源,导致迟迟无法执行。

解决方案:

  • 死锁:避免嵌套锁、使用定时锁等手段预防。
  • 活锁:使用“随机等待时间”打破同步,例如 Raft 协议中的随机选举等待。
  • 饥饿
    • 使用公平锁(先来先服务)
    • 降低锁持有时间
    • 提高线程优先级的公平性

1.3. 性能问题(Performance)

度量性能的指标:

  1. 吞吐量:指的是单位时间内能处理的请求数量。吞吐量越高,说明性能越好。
  2. 延迟:指的是从发出请求到收到响应的时间。延迟越小,说明性能越好。
  3. 并发量:指的是能同时处理的请求数量,一般来说随着并发量的增加、延迟也会增加。所以延迟这个指标,一般都会是基于并发量来说的。例如并发量是 1000 的时候,延迟是 50 毫秒。

锁带来的问题:

  • 过度串行化会削弱多线程的优势。
  • “锁”本质上是将并发转为串行,从而影响性能。

理论支持:阿姆达尔定律(Amdahl's Law):

加速比公式如下:

  • S=1/((1−p)+p/n)
  • p: 可并行的百分比
  • n: CPU 核数

举例:如果串行部分为 5%(p=0.95),无论多少线程,加速比最多为 20 倍。

提升性能的常用技术:

1. 无锁并发(Lock-Free)

    • 线程本地存储(ThreadLocal)
    • 写时复制(Copy-on-write)
    • 乐观锁
    • 原子类(如 AtomicInteger
    • Disruptor 等无锁队列

2. 减小锁持有时间

    • 使用细粒度锁(如 ConcurrentHashMap 的分段锁)
    • 使用读写锁(ReadWriteLock)

2. 管程(Monitor)

小结:

  • 管程 = 封装共享资源 + 控制互斥访问 + 条件变量实现线程协作
  • Java 使用的是 MESA 模型,synchronized 实际就是一种简化的管程。
  • 管程能解决互斥 + 同步两个核心问题,掌握管程 = 掌握并发基础
  • 学好管程,对理解各种并发工具类的底层实现极有帮助。

2.1. 管程的概念

管程的定义:

管程是一种管理共享变量及其操作的同步机制,使类的操作在多线程环境中是线程安全的。

  • Java 中的 synchronizedwait()notify()notifyAll() 正是管程模型的体现。
  • 管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。
  • 但管程更易使用,Java 优选之。

在管程的发展史上,先后出现过三种不同的管程模型:

  1. Hasen 模型
  2. Hoare 模型
  3. MESA 模型( Java 管程的实现参考)

2.2. MESA模型

并发编程核心问题:

  1. 互斥(Mutual Exclusion):同一时刻只能有一个线程访问共享资源。
  2. 同步(Synchronization):线程间如何协调、通信。

管程的解决方案:

1. 互斥:

  • 将共享变量和其操作封装于管程中(如 enq()deq())。
  • 只允许一个线程进入管程方法,其它线程等待,确保线程安全。
  • 管程与面向对象思想高度契合

2. 同步:

  • 引入条件变量(Condition Variable)等待队列
  • 若条件不满足,线程进入条件变量对应的等待队列。
  • 条件满足后,其他线程调用 notify() / signal() 通知等待线程。

  • 必须在 while 循环中使用 wait(),这是 MESA 管程模型的编程范式。
  • 除非经过深思熟虑,否则尽量使用 notifyAll()。使用 notify() 需要满足以下三个条件:

所有等待线程拥有相同的等待条件;

所有等待线程被唤醒后,执行相同的操作;

只需要唤醒一个线程。

MESA 与其他管程模型对比:

模型

执行逻辑说明

Hasen

notify()必须放末尾,唤醒后自己结束执行,等待线程接着执行

Hoare

notify()后自己阻塞,让等待线程立即执行,等对方执行完再唤醒自己

MESA

notify() 后自己继续执行,等待线程只是从条件队列进入入口等待队列,再等待调度

MESA 好处:

  • 逻辑简单,无需将 notify() 放最后
  • 效率较高,无额外的阻塞/唤醒

副作用:

  • 唤醒的线程未必能立即执行,可能条件不再满足 → 必须再次检查条件(while 循环)

2.3. Java的管程

Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。

  • Java 内置的管程方案(synchronized)使用简单,synchronized 关键字修饰的代码块,在编译期会自动生成相关加锁和解锁的代码,但是仅支持一个条件变量;
  • Java SDK 并发包实现的管程支持多个条件变量,不过并发包里的锁,需要开发人员自己进行加锁和解锁操作。

特性

Java 内置(synchronized)

并发包(Lock + Condition)

条件变量数量

一个

支持多个

是否自动加锁

是(自动生成字节码)

否(手动加锁)

使用难度

简单

稍复杂,但更灵活

http://www.xdnf.cn/news/877303.html

相关文章:

  • LangChain深度解析:LLM应用开发利器
  • Redis常见使用场景解析
  • 【C语言个数最大最多】2022-4-1
  • 网络攻防技术十二:社会工程学
  • Mysql选择合适的字段创建索引
  • Java Lombok @Data 注解用法详解
  • 量子通信:从科幻走向现实的未来通信技术
  • 四、Sqoop 导入表数据子集
  • 使用C++调用python库
  • 东西方艺术的对话:彰显中国传统艺术之美与价值
  • 主流Agent开发平台学习笔记:扣子罗盘coze loop 功能拆解
  • Vue插件
  • 租物理服务器,如何避开 “高价陷阱”
  • MES管理系统的核心数据采集方式有哪些
  • Linux 环境下 PPP 拨号的嵌入式开发实现
  • CMake在VS中使用远程调试
  • python实现合并多个dot文件
  • linux系统--iptables实战案例
  • 在本地电脑中部署阿里 Qwen3 大模型及连接到 Elasticsearch
  • if(!p)等价于 if(p==0)
  • 【学习笔记】Python金融基础
  • 猎板硬金镀层厚度:新能源汽车高压系统的可靠性基石
  • 压测软件-Jmeter
  • socket是什么
  • SQL进阶之旅 Day 14:数据透视与行列转换技巧
  • 综合案例:斗地主
  • Serverless 在商城活动页面的应用:快速扩缩容与成本控制——基于云函数的秒杀活动场景实践
  • 幂等性:保障系统稳定的关键设计
  • Sentry 的部署方式:自托管与 SaaS 服务
  • arduino D1 UNO R3 使用记录(保姆级教程)