美文网首页
从一个经典案例分析线程可见性

从一个经典案例分析线程可见性

作者: dsjaikdnsajdnua | 来源:发表于2022-11-03 16:01 被阅读0次

从一个经典案例分析线程可见性

为什么需要深入底层?

  1. 机械同感(来源于赛车,越清楚赛车的结构就越能发挥出赛车的性能)

举例mysql B+树-----------为什么like %在 左边就没办法使用索引、为什么日期格式化了就用不了索引、为什么联合索引会断(介绍索引桥)、为什么innodb 适合95%的情况

不懂原理的知识就像是漂浮在空中的气球,很容易被吹走。

  1. 可以装逼

原始代码

**VisibilityTest**,理想的效果:启动线程A,一直count,然后启动线程B去停止线程A的While循环
思考

为什么不可以??因为线程安全问题。那么为什么会有这个线程安全的问题???

如何让线程B干预线程A??

使用volatile

为什么可以?volatile语义-保证了可见性

  • 写:当写一个volatile修饰得变量,JMM会把该线程对应的共享变量刷新到主存中:
  • 读:当读一个volatile修饰得变量,JMM会把该线程对应的本地内存设置为无效,线程直接读取主存中的变量:
思考?

在我们多个服务之间,是如何同步数据的,等类似的场景??

是否会在本地维护一份数据副本,以提高性能。

  1. 使用同一个地方存储
  2. 消息队列

其实宏观世界是如此,微观世界也是一样的。

插入---JMM模型简介

CPU模型

为了彻底压榨CPU性能,引入高速缓存L1,L2,L3(L1,L2是核心特有的,每颗核心都有。但是L3是核心共享的)查找顺序:L1--L2----->L3----->内存---->硬盘

image-20221103175153987.png

JMM模型

image-20221103151515169.png

所以为什么可以???

因为:FULL_MEM_BARRIER(内存屏障) bytecodeInterpreter.cpp >>>>>>> orderAccess_linux_x86(只看linux x86 cpu下的实现) 【思考------单核CPU是否存在以上问题???】 >>>>> fence实现。

本质:lock; addl $0,0(%%esp) 这个lock前缀指令,会等待它之前得所有指令完成,并把缓存所有得写操作写回内存,之后开始执行,,并且因为缓存一致性协议,刷新缓存(sotre buffer)得操作会导致其他得cache副本失效。

使用System.out.println(count);

为什么可以?synchronized,这也就是为什么snor不允许使用sout的原因,性能低。

那么为什么synchronized可以让共享变量变得让线程之间可见??

因为:FULL_MEM_BARRIER(内存屏障) objectMonitor.cpp --->

JDK的差异演示

为什么client可以?因为client一般认为不追求性能。

详细的分析过程:JDK Client & Server 变量可见性问题 - 简书 (jianshu.com)

我认为,Server模式比Client 的变量做了更多性能的优化,比如这个成员变量。Server为了更高的性能,貌似不会保证成员变量在多线程环境下的一致性,需要指明:volatile才能保证多线程环境下的一致。(多线程在不同的核心进行计算,变量是在寄存器中参与运算的,不同核心运算(多线程下)之间的运算会有变量的副本,这就导致了不同线程实际上操作的变量不是同一份,这就是线程不安全,但是把寄存器的变量刷回主存,同时还需要对其他线程加锁才行,性能比较低,所以Server就默认采用了"牺牲一致性,保证性能"的方案把,Server需要手动添加volatile保证线程安全。目测Client 版本牺牲了性能保证了一致性,所以多线程操作的相当于操作同一份变量。)

使用Integer

为什么可以??查看Integer源码---->本质是一个final 类型的int ,final 的语义,代表着不可变,就意味着,也有内存屏障(全屏障)

编译器会在final域的写之后,插入一个StoreStore屏障.编译器会在读final域操作的前面插入一个LoadLoad屏障

查看Integer的自动装箱拆箱

(2条消息) Integer类自动拆箱,装箱解析_温JZ的博客-CSDN博客_integer自动拆箱

在count上面加volatile

为什么可以??

读:当读一个volatile修饰得变量,JMM会把该线程对应的本地内存设置为无效,线程直接读取主存中的变量:

思考:什么是内存屏障??

插入概念:指令重排。多核之间的指令重排没办法保证。

写内存屏障:写的数据一定要写入到主内存!让其他线程可见。

读内存屏障:强制让缓存失效,去读取主内存的内容,这样其他线程修改的数据就可见了。

思考:以上都是本质都是通过内存屏障(MEM_BARRIER)实现的可见性,那么还有没有其他方式去实现??

思考:既然是CPU内部的是缓存,那么会不会有淘汰时间??

使用长时间停顿(大于9000ns)

VisibilityTestLongSleep

使用短时间停顿(小于9000ns)

VisibilityTestShortSleep



通过上下文切换

通过Thread.yield

VisibilityTestYield

Thread.yield 就是让出时间片

通过Thread.sleep

VisibilityTestThreadSleep

总结

线程安全本质就是对内存使用的安全。

单核CPU下不存在线程安全不安全的问题。那思考??为什么不把CPU的核心做的很大,直接单核就完事了??

相关文章

  • 从一个经典案例分析线程可见性

    从一个经典案例分析线程可见性 为什么需要深入底层?机械同感(来源于赛车,越清楚赛车的结构就越能发挥出赛车的性能)举...

  • volatile的分析

    volatile的分析 可见性 可见性指的是线程之间的可见性,一个线程修改的状态对于另一个线程是不可见的。 vol...

  • 多线程 | Volatile到底有什么用?

    Volatile的作用: 保持内存可见性.内存可见性:多个线程操作同一个变量,可确保写线程更新变量,其他读线程可以...

  • 聊聊Java多线程之内存可见性

    可见性(Visibility) 线程可见性简介 线程之间的可见性是指当一个线程修改一个变量,另外一个线程可以马上得...

  • 02|Java内存模型

    可见性、原子性、有序性是并发问题的三个关键因素。 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。 可...

  • 【Java进阶】并发编程

    . 概述 三种性质 可见性:一个线程对共享变量的修改,另一个线程能立刻看到。缓存可导致可见性问题。 原子性:一个或...

  • volatile

    volatile 1.可见性可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修...

  • 第三章——对象的共享

    3.1 可见性 3.1.3 加锁与可见性 内置锁可以用于确保某个线程以一种可预测的方法来查看另一个线程的执行结果。...

  • ThreadLocal

    ThreadLocal入门 模仿PageHelper案例 结果 原理分析(总结) 万物接对象,当前线程也是对象,可...

  • 10、synchronized与volatile

    1、可见性与原子性 可见性 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得...

网友评论

      本文标题:从一个经典案例分析线程可见性

      本文链接:https://www.haomeiwen.com/subject/idqutdtx.html