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

多线程安全与通信问题

线程安全问题

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

    如图所示,在代码运行时,每一个线程并不会对堆内存中的变量本身进行操作,而是先复制一个副本放在本地变量表中(加载到自己的工作内存中),随后对这个副本进行操作,操作完毕后再将这个副本的内容赋给堆内存中的对象本体。
  • 出现线程安全问题的原因: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时间更精准、更规范

  • 运行截图
http://www.jsqmd.com/news/343236/

相关文章:

  • 异步批处理优化:DeepSeek API调用成本降低60%实战技巧
  • 2026代码趋势前瞻:DeepSeek-V4 mHC架构实操与复杂工程逻辑无缝转化之道
  • [信息论与编码理论专题-14]:数字通信系统的核心目标是高效、可靠地传输信息,而信息论正是为解决这一根本问题而诞生的。因此,信息论首先在通信领域得到系统性应用,并成为现代数字通信系统的理论基石。
  • 苹果“折叠矩阵”谋划:从口袋方块到20寸工作站,柔性形态的全新升级!
  • [信息论与编码理论专题-17]:信息熵是信源进行无损编码时,平均码长所能达到的理论下限。各事件的概率决定了其在最优变长编码(如霍夫曼编码)中的码长:概率越大,码长越短;概率越小,码长越长。
  • Promise 未捕获 reject 错误处理指南
  • Clawdbot贾维斯,2026微信AI合规方案!企业微信+AI助理零封号部署(官方接口版)
  • 基础版够用吗?10款AI效率加速器的专业功能对比
  • AI效率工具升级指南:10款产品的专业版功能解析
  • 10款AI工具对比:基础版与专业版的功能差异一览
  • 解锁AI效率工具的高级功能:10款专业版优势分析
  • Java反序列化CommonsCollections篇CC6-最好用的CC链
  • 基于AI的软件工程毕设方案:8款工具优化写作与开发流程
  • 告别繁琐!GISBox一键搞定地形下载与3DTiles导出,效率提升90%
  • Excel周数计算终极方案:用数字调节钮与数组公式构建动态日期分析系统
  • 从基础到专业:10款AI效率加速器的功能升级详解
  • 吐血整理!大模型基础知识点全梳理(非常详细),含学习资料包。
  • 细胞电生理仿真软件:GENESIS_(6).模型构建基础:膜和通道
  • 后端的学习
  • Graph-O1:基于蒙特卡洛树搜索与强化学习的文本属性图推理框架
  • 如何在 Vim 启用行号显示和语法高亮机制
  • 《透视 ImGui:从底层原理到面试通关》第九讲:多视图与 Docking —— 构建专业级工具界面
  • ARM 架构中的CurrentEL
  • 细胞电生理仿真软件:GENESIS_(3).安装与配置GENESIS环境
  • Scala 变量
  • 在线教程|DeepSeek-OCR 2公式/表格解析同步改善,以低视觉token成本实现近4%的性能跃迁
  • 华为OD技术面真题 - 计算机网络相关 - 4
  • 【c++】glibc内存管理
  • DOM 事件
  • 2026年正规的京东e卡回收,京东e卡,礼品卡回收公司选购选型指南 - 品牌鉴赏师