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

从0开始理解并发、线程与等待通知机制(下)

线程安全问题与synchronized机制

  • 线程安全问题演示

    • 示例类SynTest定义一个count变量。
    • 启动两个线程对count进行累加操作,预期结果为2万。
    • 实际运行结果小于2万且不稳定,表明线程不安全。
  • 原因分析

    • 多线程同时访问共享变量导致不可预料的结果。
    • 解决方案:使用synchronized关键字加锁。
  • synchronized使用方式

    • 方法级别加锁
      • 在方法返回值前添加synchronized关键字。
      • 该锁对象为当前实例(this)。
    • 代码块加锁
      • 使用synchronized(this)synchronized(obj)指定锁对象。
      • 更灵活,适用于部分代码加锁场景。
  • 对象锁与类锁的区别

    • 对象锁:用于成员方法或同步块中,锁对象为具体实例(如this或任意Object)。
    • 类锁:用于静态方法或锁住类的class对象。
    • 关键原则:多个线程必须持有同一把锁才能实现互斥访问共享资源。
  • 错误使用synchronized案例

    • 若两个线程分别使用不同的锁对象(如new Object()),则无法保证线程安全。
    • 必须确保多个线程使用的锁对象是同一个。

volatile关键字详解

  • 应用场景

    • 适用于一写多读的并发场景。
    • 例如主线程修改某个标志位,其他线程感知变化并作出响应。
  • 作用与限制

    • 可见性:一个线程对变量的修改对其他线程立即可见。
    • 有序性:防止指令重排序优化。
    • 不能保证原子性:无法替代synchronizedAtomicInteger等原子类。
  • 为何不是锁机制?

    • volatile仅提供轻量级通信与同步机制,无加锁过程。
    • 不具备互斥能力,不能解决多线程同时写共享变量的安全问题。
  • 字节码层面解释

    • 对于Integer count变量,每次count++都会创建新的Integer对象。
    • 导致即使加锁,也可能因锁对象变化而失效。
    • 可通过打印哈希码验证对象地址是否变化。
  • volatile vs synchronized

    • synchronized既能保证可见性,又能保证原子性和有序性。
    • volatile只能保证可见性和有序性。

等待通知机制标准范式

  • 生活类比:快递通知机制

    • 类似快递到达驿站后系统发短信通知用户取件。
    • 避免轮询浪费资源。
  • 核心API:Object类中的wait与notify

    • wait():使线程进入等待状态,释放锁。
    • notify()/notifyAll():唤醒一个或所有等待线程。
    • 必须在synchronized块中调用,否则抛出异常。
  • 标准范式

    • 等待方:

      java

      synchronized(锁对象) { while (条件不满足) { 锁对象.wait(); } // 条件满足,继续执行业务逻辑 }
    • 通知方:

      java

      synchronized(锁对象) { // 修改条件 锁对象.notifyAll(); }
  • 注意事项

    • wait()会释放锁,notify()不会立即释放锁,需等到同步块执行完毕。
    • 必须在while循环中检查条件,避免虚假唤醒。

实战案例:模拟快递运输与连接池

  • 快递运输案例

    • 主线程模拟快递运输,每增加100公里唤醒一次。
    • 子线程监听公里数变化,更新数据库;另一子线程监听是否到达目的地并通知用户。
  • 连接池实现

    • 使用等待通知机制管理数据库连接池。
    • 支持超时等待模式,增强灵活性。
    • 核心思想:空闲时等待,有连接可用时通知唤醒。
  • 等待超时模式实现

    • 记录超时时间戳。
    • 每次被唤醒后重新计算剩余等待时间。
    • 时间归零则退出等待,抛出异常。

面试高频题解析

  • 线程相关方法与锁的关系

    • sleep():不释放锁。
    • yield():不释放锁。
    • wait():释放锁。
    • notify()/notifyAll():不释放锁,同步块执行完才释放。
  • 为什么wait/notify必须在同步块中调用?

    • 避免lost wake-up问题。
    • 若不在同步块中调用,可能导致通知丢失,线程永远阻塞。
  • 为什么要在while中检查条件?

    • 多线程环境下可能出现虚假唤醒
    • 使用while循环确保条件真正满足后再继续执行。

补充知识:CompletableFuture

  • 背景

    • JDK1.8引入,用于简化Future任务编排。
    • 支持链式调用、组合任务、异步处理等。
  • 核心功能

    • 任务编排:支持任务之间的依赖关系(thenApply, thenAccept等)。
    • 异步执行:支持自定义线程池。
    • 组合任务:allOf, anyOf等。
  • 接口结构

    • CompletableFuture<T>实现了Future<T>CompletionStage<T>接口。
    • 提供丰富API用于任务控制和组合。

答疑总结

  • 关于notify与notifyAll

    • notify()随机唤醒一个等待线程。
    • notifyAll()唤醒所有等待线程。
    • 实际开发中更推荐使用notifyAll(),避免遗漏唤醒目标线程。
  • CompletableFuture是否能替代线程池?

    • 不能完全替代,它只是任务编排工具。
    • 仍需线程池执行实际任务。
  • 同步机制的本质

    • 同步即协调多个线程按顺序访问共享资源。
    • synchronizedvolatile均可实现同步,但侧重点不同。
  • volatile如何保证可见性?

    • 插入内存屏障,刷新CPU缓存到主存。
    • 所有读线程均可看到最新值。
  • 类锁能否继承?

    • 不可以,锁是基于对象或class对象,不具有继承特性。
http://www.cnnetsun.cn/news/1448266.html

相关文章:

  • MAI-UI-8B效果实测:输入需求,直接输出可运行的前端代码
  • 避坑指南:在超微主板上用Nvidia显卡跑Ubuntu24.04的那些玄学问题
  • 避坑指南:Xilinx XDMA驱动交叉编译到ARM平台常见的5个错误及解决方法
  • Unity游戏开发实战:如何用阿里云语音API实现智能NPC对话(附完整C#代码)
  • Zotero Tag插件保姆级配置指南:用Emoji标签搞定文献阅读状态(Win11字体修复)
  • M2LOrder模型Matlab算法仿真与代码转换实战教程
  • T2T基因组组装实战:如何利用Hi-C数据提升染色体水平组装质量(附最新研究案例)
  • Supabase 自部署实战:从入门到精通
  • Ubuntu下基于Bind9构建负载均衡DNS解析服务
  • HybridBlocks终极指南:深度学习效率优化新范式
  • 利用EVA-02重构技术文档:将零散笔记整理成结构化开发手册
  • BM62S2301-1热式风速传感器原理与Arduino驱动深度解析
  • TensorFlow文本距离计算终极指南:编辑距离与地址匹配实战
  • 小智ESP32服务器终极指南:如何构建元宇宙健身平台与智能教练系统
  • TypeScript与Just.js完美结合:终极类型安全开发指南
  • 如何将VS Code插件市场的Deno插件安装到Trae?完整配置流程
  • 终极指南:如何将jrnl官方文档完美本地化
  • unordered_map与unordered_set
  • 专访越擎科技,为什么选择iRobotCAM机器人离线编程软件作为机器人激光加工首选方案
  • 终极指南:Pachyderm数据分区技术如何优化查询性能10倍
  • OpenClaw私有化部署Qwen3-VL:30B:飞书助手
  • 30秒实现前端预取引擎:json-server性能优化实战指南
  • TFMPI2C库:TFMini-Plus的I2C嵌入式驱动设计与工程实践
  • SQL Studio界面定制教程:打造个性化数据库工作环境
  • 深入理解x86架构:CR0寄存器各比特位的实战应用与调试技巧
  • Gemma-3-12B-IT WebUI案例展示:requests代码安全加固+超时重试添加
  • Dockerize故障恢复终极指南:快速诊断和解决容器启动问题
  • 掌握Mongoose Discriminator模式:多态数据建模的终极指南
  • SEO_避开这些常见误区才能真正做好SEO优化
  • VUE3子组件方法暴露实战:从定义到父组件调用的完整指南