JAVA内存模型 (JMM)和同步以及可见性

时间:2021-12-13 20:51:53

可见性:
   一般来说,我们对可见性的定义是一个线程对共享变量值的修改,能够被其他线程 及时的看到,那么这个时候我们一般称这个变量是线程间可见的。
那么什么是共享变量?       一般来讲,如果一个变量在多个线程的工作内存中都存在有副本,那么这个     变量就是这几个线程的共享变量。
 那么啥是工作内存呢? 这个时候就是我们的JMM登场了。 Java内存模型(JMM)。描述了Java程序中各种线程共享变量的访问规则,以及在JVM中将变量存储到内存和从内存中读取出来变量这样的底层细节。 在多核系统中,处理器一般有一层或多层的缓存,这些的缓存通过加速数据访问,和 降低共享内存在总线上的通讯。来提高cpu性能。 在处理器层面上,内存模型定义了一个充要条件,“让当前的处理器可以看到其他处理器写入到内存的数据”  以及 “其他处理器可以看到当前处理器写入到内存的数据”。
有些处理器有很强的内存模型,能够让所有的处理器在任何时候任何指定的内存地址上都可以看到完全相同的值,而有的处理器内存模型较弱,在这种处理器中,必须需用内存屏障。来刷新本地处理器缓存并使本地处理器无效。目的是为了让当前处理器能够看到其他处理器的写操作或让其他处理器看到当前处理器的写操作。
在JMM中所有的变量都存储在主内存中,且每个线程都有自己独立的工作内存,里面保存着该线程使用到的变量的副本。(其实就是主内存的一份拷贝)
JAVA内存模型 (JMM)和同步以及可见性



Class Reordering{
int x=0, y=0;
public void writer(){
x=1;
y=2;
}
public void reader(){
int r1=y;
int r2=x;
}
}
如果在并发中执行这段代码, 读取Y变量将会得到2这个值。 因为这个写入比写到X变量更晚一些。 但是如果重排序发生了, 那么就能发生对Y变量的写入操作,读取2个变量
的操作紧随其后,而且写入到x这个操作能发生程序的结果可能是r1=2 r2=0;

当然不管编译器如果进行重排序, 也要遵循as-if-serial语义。
as-if-serial语义的意思是: 无论如何重排序,程序执行的结果应该与代码顺序执行的结果是一样的。 这点我们一般靠java编译器在单线程下做保证。 打个比喻:
int num1=1; //1int num2=2; //2int sum=num1+num2;//3

 在单线程的情况下, 第一 第2 他们的顺序是可以进行重排序的, 但是第三行是不行的. 所在在单线程的情况下, 我上面所讲的霹雳啪啦的一大堆理论等于放屁, 别急我们看下面。
在多个线程访问下, as-if-serial是没有作用的, 按照一般情况,一个线程不会关注其他线程正在做什么。无论其他线程在计算还是在读写, 他都不太关心。 但是等他需要关心其他线程在做什么的时候, 就需要做一个事情了, 这个事情就是同步!~从java内存模型的角度讲,没有正确同步的含义是    1.一个线程中有一个对变量的写操作     2.另外一个线程对同一个变量有读操作     3. 而且写操作和读操作没有通过同步保证顺序
当这些规则被违反的时候,我们就说在这个变量上有一个“数据竞争”(data race)。一个有数据竞争的程序就是一个没有正确同步的程序。

java在语言层面支持可见性和同步的方式是通过:  synchronized, volatile. 来实现的。 其中volatile并不能实现可见性。我这里说的并没有包括jdk1.5以后加入的并发包。
JMM对synchornized 有二条规定: 1.线程解锁前,必须把共享变量的最新值刷新到主内存中    2.线程加锁时, 将清空工作内存*享变量的值,从而使用共享变量时需要从    主内存中重新读取最新的值。
所以在synchronized代码块退出的时候就保证了主内存是最新的值, 在加锁时就保证
 了加载的是最新的值。 现在大家应该知道synchronized 为啥能同步了吧。

其实synchornized 可以保证可见性。 应该会有人很奇怪为啥sync锁还能保证可见性。 下面我来说不可见的原因有哪些。在多线程环境下能够造成不可见的原因一般有三点: 1. 线程交叉执行。 2. 重排序结合线程交叉执行.3.共享变量没有及时更新
首先synchronized 已经保证了线程交叉执行的正确性,  所以第一条没用。 而关于第二条嘿嘿嘿,我上面也写了 单线程情况下不管你怎么重排序都要保证结果一致性。 所以也废除。 第三.....  大家应该能猜得到。  

大家是不是觉得stnchronized 很无敌很牛逼了?  其实他也是有缺点的, 不然不会有大名鼎鼎的java.util.concurrent并发包的出现。  我以我的理解说几点sync的坏处, 第一 synchronized粒度太粗, 而且该关键字没有提供当线程没获取到锁的情况下的超时逻辑, 而且。 我们应该知道, 一个只读变量他在多线程坏境下是安全的, 因为他并不会修改变量的值,所以捏。 sync锁应该给我们提供一个对互斥区域的并发读。 但是很遗憾他并没有。。。。
 volatile 这个关键字,他可以实现内存的可见性, 深入点来讲,其实他就是加入了内存  屏障和禁止重排序优化来实现的, 所以我上面所说的不完全都是废话..... 当我们 对volatile变量执行写操作的时候,会在写操作后加入一条store屏障指令,他会把cpu写缓存器的缓存强制写入的主内存中去。
当对volatile变量执行读操作时,会在读操作前加入一条load屏障指令,他呢,也会强制cpu读到主内存中去。
 其实在java内存模型中一共定义了8条操作指令。他们负责和工作内存打交道。具体的指令..... 我忘了...  大家可以查一下。太晚了,很多东西记不起。有空在更新一下。