news 2026/4/28 5:28:33

多线程安全与通信问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多线程安全与通信问题

线程安全问题

  • 当多个线程操作(读/写)同一份数据时,可能会出现线程安全问题
  • 进程的内存图:

    如图所示,在代码运行时,每一个线程并不会对堆内存中的变量本身进行操作,而是先复制一个副本放在本地变量表中(加载到自己的工作内存中),随后对这个副本进行操作,操作完毕后再将这个副本的内容赋给堆内存中的对象本体。
  • 出现线程安全问题的原因:CPU在进行操作时,并不是一个线程完全执行完在进行下一个,而是为了提高CPU的计算效率,防止CPU将大部分时间浪费在等待上,采用时间片轮转的方法,即每个线程执行一小段时间就换下一个的方式来计算。在这种情况下执行count++的操作时,线程1和线程2会对堆内存中的数据不停的进行修改,而且会不停的覆盖对方的计算结果,导致计算结果不准确,引发线程安全问题。
  • 代码模拟如下:
packagecom.nwu.by0204_ThreadSafe;classThreadDemo2extendsThread{@Overridepublicvoidrun(){for(inti=0;i<10000;i++){ThreadDemo1.count++;}}}publicclassThreadDemo1extendsThread{staticintcount=0;@Overridepublicvoidrun(){for(inti=0;i<10000;i++){count++;}}}classMain{publicstaticvoidmain(String[]args){ThreadDemo1threadDemo1=newThreadDemo1();ThreadDemo2threadDemo2=newThreadDemo2();threadDemo1.start();threadDemo2.start();try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(ThreadDemo1.count);}}
  • 代码中用到的static关键字
    static意思是静态的,静态变量只在类加载的时候获取一次内存空间,因此代码中的任何对象的修改都会被保留。同时,静态变量的访问需要用类名来访问。
  • 代码中使用sleep方法的解释:
    线程的进行需要时间,在调用线程对象的start方法后,程序会立即打印输出语句,此时的线程还处于就绪状态,因此此时打印出来的count就是0。解决方案:使用sleep方法,让程序等待一秒在打印count的值
  • 运行结果如下:

    结果并非是预期的20000,这就是线程的安全问题。

线程安全问题的解决方案

  • 原子性:一个或多个操作,要么在执行时不会被打断,要么就不执行
  • 原子操作:不会被线程调度所打断的操作
  • 可见性:当多线程访问同一变量时,一个线程修改该变量的值,另一个线程能立刻看见修改的值
  • 为了保证可见性,可以给变量加一个修饰词volatile,加上这个关键字后,这个变量就具备了可见性
  • 为了保证线程安全,Java中有内置锁:synchronized 同步锁
    • synchronized(){ }
    • 参数 :必须是一个当前所有线程都可以访问的唯一对象
    • 当前线程在执行代码块中的内容时,其他所有线程必须等待,直到代码块中的内容执行完毕
    • 等代码块执行完成后会解锁,其他线程继续与该线程进行竞争
  • 代码修改方案:给两个线程的run方法里要执行的代码都加上一把synchronized同步锁即可,同时还需要在new一个静态的对象,让两个线程都可以访问
packagecom.nwu.by0204_ThreadSafe;classThreadDemo2extendsThread{@Overridepublicvoidrun(){synchronized(ThreadDemo1.obj){for(inti=0;i<10000;i++){ThreadDemo1.count++;}}}}publicclassThreadDemo1extendsThread{staticintcount=0;staticObjectobj=newObject();@Overridepublicvoidrun(){synchronized(obj){for(inti=0;i<10000;i++){count++;}}}}classMain{publicstaticvoidmain(String[]args){ThreadDemo1threadDemo1=newThreadDemo1();ThreadDemo2threadDemo2=newThreadDemo2();threadDemo1.start();threadDemo2.start();try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(ThreadDemo1.count);}}
  • 执行结果

代码的最终优化

  • 在源代码中,为了保证线程执行完后在进行打印输出语句,采用的方法是调用Thread类中的sleep方法,让程序停一秒,等待线程执行。更规范的做法是采用join()方法
  • join方法是Thread类中的实例方法,它的作用是让其他线程都要等待这个线程执行完毕后在进行下一步操作,需要在start方法后执行。
  • 采用join方法而不是sleep方法的好处:sleep方法是我们人为的猜出一个等待方法,规定程序需要等待多少ms,而join方法是系统自己判断的,只要该线程执行完毕,就可以开始下一个线程,比认为规定更加精准、规范
  • 代码修改后:
packagecom.nwu.by0204_ThreadSafe;classThreadDemo2extendsThread{@Overridepublicvoidrun(){synchronized(ThreadDemo1.obj){for(inti=0;i<100000;i++){ThreadDemo1.count++;}}}}publicclassThreadDemo1extendsThread{staticintcount=0;staticObjectobj=newObject();@Overridepublicvoidrun(){synchronized(obj){for(inti=0;i<100000;i++){count++;}}}}classMain{publicstaticvoidmain(String[]args){ThreadDemo1threadDemo1=newThreadDemo1();ThreadDemo2threadDemo2=newThreadDemo2();threadDemo1.start();threadDemo2.start();try{threadDemo1.join();threadDemo2.join();}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println(ThreadDemo1.count);}}

在修改后的代码中,只有把threadDemo1和threadDemo2两个线程都执行完才会进行下一步(两个线程是并行等待的),也就是执行输出语句。比人为规定sleep时间更精准、更规范

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

解析muduo源码之 Poller.h Poller.cc

目录 一、 Poller.h 1. Poller 类的整体定位与核心设计目标 2. 核心成员变量解析 1. 私有成员&#xff1a;所属 EventLoop&#xff08;线程安全核心&#xff09; 2. 保护成员&#xff1a;fd 到 Channel 的映射&#xff08;核心管理结构&#xff09; 3. 公共类型别名&…

作者头像 李华
网站建设 2026/4/25 12:12:01

解锁AI效率工具的高级功能:10款专业版优势分析

&#xfffd;&#xfffd; 10大降AIGC平台核心对比速览 排名 工具名称 降AIGC效率 适用场景 免费/付费 1 askpaper ⭐⭐⭐⭐⭐ 学术论文精准降AI 付费 2 秒篇 ⭐⭐⭐⭐⭐ 快速降AIGC降重 付费 3 Aibiye ⭐⭐⭐⭐ 多学科论文降AI 付费 4 Aicheck ⭐⭐⭐⭐…

作者头像 李华
网站建设 2026/4/26 19:08:04

洛谷 P1165:日志分析 ← 双栈

【题目来源】 https://www.luogu.com.cn/problem/P1165 【题目描述】 M 海运公司最近要对旗下仓库的货物进出情况进行统计。目前他们所拥有的唯一记录就是一个记录集装箱进出情况的日志。该日志记录了两类操作&#xff1a;第一类操作为集装箱入库操作&#xff0c;以及该次入库…

作者头像 李华
网站建设 2026/4/25 20:08:24

前端判断不等于 undefined 不等于 null 的方法

前端判断不等于 undefined 不等于 null 的方法 在前端开发&#xff08;JavaScript/TypeScript&#xff09;中&#xff0c;判断一个变量既不等于 undefined 也不等于 null&#xff0c;通常被称为判断“空值”或“有效存在”。 以下是几种常用且推荐的方法&#xff0c;你可以根…

作者头像 李华
网站建设 2026/4/27 7:16:37

MCP4725 I2C 12位数模DA转换模块原理图设计,已量产

目录 1、核心芯片:MCP4725 为什么是首选? 2、原理图深度拆解:每一处设计都有讲究 2.1、电源系统:LP5907 带来的稳定供电 2.2、DAC 核心电路:MCP4725 的极简设计 2.3、I2C 接口与地址配置:灵活适配多设备级联 2.4、低功耗设计:<0.2mA 的 “隐形优势” 在 Arduin…

作者头像 李华
网站建设 2026/4/25 0:25:22

Palantir 第四季度财报深度解读CEO致股东信:43亿美元订单,70%营收增长,AI驱动下的惊人增长与企业级AI技术帝国的宏伟愿景

摘要 Palantir CEO亚历克斯卡普在2026年2月2日发布的致股东信中,宣布公司第四季度营收达到创纪录的14亿美元,同比增长70%,美国市场营收达11亿美元,同比增长93%。公司单季利润达6.09亿美元,环比增长28%。信中强调了AI技术的实际应用价值,批判了行业内的浮夸风气,阐述了技术如何…

作者头像 李华