栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程可见性,有序性 volatile

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程可见性,有序性 volatile

可见性问题简述

两个线程对同一个变量修改(类似脏读),主线程对变量修改,变量对t1是不可见的

有序性问题简述

CPU/JVM层面会优化指令执行顺序,引入了StoreBuffer异步通知的方式,导致线程中的执行顺序可能会发生改变(两个指令没有强关联关系会导致),在缓存获取数据以及通知其他cpu的过程中,导致后面的指令先执行了;

int a=0;
void test(){
  a=1;
  b=a+1;
  assert(b==2);//false
}
//指令重排
b=a+1;
a=1;
如何解决

volatile通过缓存锁、总线锁(根据缓存一致性协议MESI)以及内存屏障解决的多线程中的可见性以及有序性问题的

可见性问题

产生原因

因为执行速度:CPU > 内存 > 磁盘

所以为了提高CPU资源利用率(cpu等待内存或磁盘响应),通过下面三方面方式

1.CPU增加高速缓存,减少等待时间

2.操作系统中,增加进程、线程,通过CPU的时间片切换,提升cpu利用率

3.编译器,JVM的深度优化活性失效

最终因为高速缓存(三级缓存)引起问题

  • 缓存一致性问题--因为线程的变量先从缓存再同步到主存,其他线程何时从主存同步时机未知,会导致不同线程中的缓存不一致,通过缓存锁或总线锁(根据缓存一致性协议MESI)调用汇编语言的lock,我们程序使用volatile来触发

  • 伪共享问题--每个缓存里面都是由缓存行组成的,缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享,为了避免多个线程同时操作一个缓存行进行读写(使用对齐填充将空间都占满64,不让其他cpu来使用)空间换时间解决,使用注解@Contended

缓存一致性问题

MESI协议状态介绍

MESI中每个缓存行都有四个状态,分别是E(exclusive)、M(modified)、S(shared)、I(invalid)。

M:修改态,代表该缓存行中的内容被修改了,并且该缓存行只被缓存在该CPU中。

E:独享态,E代表该缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。这个状态的缓存行中的内容和内存中的内容一致。该缓存可以在任何其他CPU读取该缓存对应内存中的内容时变成S状态。或者本地处理器写该缓存就会变成M状态。

S:共享态,该状态意味着数据不止存在本地CPU缓存中,还存在别的CPU的缓存中。这个状态的数据和内存中的数据是一致的。当有一个CPU修改该缓存行对应的内存的内容时会使该缓存行变成 I 状态。

I:无效态,代表该缓存行中的内容时无效的。

MESI工作原理

1、线程A从内存中将变量a加载到缓存中,并将变量a的状态改为E(独享),并通过总线嗅探机制对内存中变量a的操作进行嗅探

2、线程B读取变量a,总线嗅探机制会将线程A中的变量a的状态置为S(共享),并将变量a加载到线程B的缓存中,状态为S

3、线程A对变量a进行修改操作,此时线程A中的变量a会被置为M(修改)状态,而线程B中的变量a会被通知,改为I(无效)状态,此时线程B中的变量a做的任何修改都不会被写回内存中(高并发情况下可能出现两个CPU同时修改变量a,并同时向总线发出将各自的缓存行更改为M状态的情况,此时总线会采用相应的裁决机制进行裁决,将其中一个置为M状态,另一个置为I状态,且I状态的缓存行修改无效)

4、线程A将修改后的数据写回内存,并将变量a置为E(独占)状态

5、此时,线程B通过总线嗅探机制得知变量a已被修改,会重新去内存中加载变量a,同时线程A和线程B中的变量a都改为S状态

内存屏障

cpu不知道什么时候不允许优化,需要开发人员自行判断处理,使用内存屏障可以控制指令不允许优化。

  • 读屏障lfence

  • 写屏障sfence

  • 全屏障mfence

JMM(java memory model)

Java内存模型的主要目标就是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的细节。针对不同的操作系统与平台提供的

Happens-before规则

(告诉你哪些场景不会存在可见性问题)

规则1.As-if-serial(程序顺序型规则):不管如何重排序,单线程执行结果一定不会发生变化

规则2.传递性规则:如果a在b之前,b在c之前,那么a在c之前

规则3.volatile变量规则:volatile变量修饰

规则4.监视器锁规则:如果线程1获得锁,并且执行完成后释放锁,线程2获取锁,他读取到的数据一定是线程1更改之后的

规则5.start规则:主线程设置变量在线程start之前,那么线程读取变量的值一定是设置后的值

规则6.join规则:join操作之后的执行,必须等待join线程执行结束后再执行

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/328912.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号