深入理解jvm-java内存模型(结合volatile)

1. 结构

在这里插入图片描述

  • 所有的变量存储在主内存
  • 每条线程都有自己的工作内存
  • 线程的工作内存保存了该线程所使用变量的主内存副本
  • 线程对变量的所有操作必须在工作内存中进行,不得直接操作主内存
  • 线程间变量的传递必须由主内存来交互完成

在这里插入图片描述

2.内存间交互原子操作

·lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。

·unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

·read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。

·load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

·use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。

·assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

·store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。

·write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

其中 如果要把一个变量从主内存拷贝到工作内存,那就要按顺序执行read和load操作,如果要把变量从工作内存同步回主内存,就要按顺序执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序执行,但不要求是连续执行。

3. volatile理解

  • 保证此变量对所有线程的可见性(一旦某个线程修改了值,新的值其他线程可以立刻得知)(并不能保证线程安全,例如: ABA 2 1 3等)
  • 禁止指令重排序

被volatile修饰的变量有以下属性:

❑ 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。

❑ 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。(例如long,double)

重排序 (也可以理解写后读,读后写,写后写)

什么是重排序:为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。
原因:一个好的内存模型实际上会放松对处理器和编译器规则的束缚,也就是说软件技术和硬件技术都为同一个目标
而进行奋斗:在不改变程序执行结果的前提下,尽可能提高执行效率。JMM对底层尽量减少约束,使其能够发挥自身
优势。因此,在执行程序时,为了提高性能,编译器和处理器常常会对指令进行重排序。一般重排序可以分为如下三
种:
1.编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序;
2.指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器
可以改变语句对应机器指令的执行顺序;
3.内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。
在这里插入图片描述

在这里插入图片描述
如果定义initialized变量时没有使用volatile修饰,就可能会由于指令重排序的优化,导致位于线程A中最后一条代码“initialized=true”被提前执行(这里虽然使用Java作为伪代码,但所指的重排序优化是机器级的优化操作,提前执行是指这条语句对应的汇编代码被提前执行),这样在线程B中使用配置信息的代码就可能出现错误,而volatile关键字则可以避免此类情况的发生

还有一个例子:
在这里插入图片描述

可能会出现i=0,j=0 情况

原因:
这里线程one和线程two可以同时把共享变量写入自己的写缓冲区(A1,B1),然后从内存中读取另一个共享变量(A2,B2),最后才把自己写缓存区中保存的脏数据刷新到内存中(A3,B3)。当以这种时序执行时,程序就可以得到x=y=0的结果。
在这里插入图片描述


volatile变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢上一些,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

4.针对于64位基本类型long和double

如果有多个线程共享一个并未声明为volatile的long或double类型的变量,并且同时对它们进行读取和修改操作,那么某些线程可能会读取到一个既不是原值,也不是其他线程修改值的代表了“半个变量”的数值。不过这种读取到“半个变量”的情况是非常罕见的

5. 先行发生原则(Happens-Before)

它是判断数据是否存在竞争,线程是否安全的非常有用的手段。

一个操作“时间上的先发生”不代表这个操作会是“先行发生”。那如果一个操作“先行发生”,是否就能推导出这个操作必定是“时间上的先发生”呢?很遗憾,这个推论也是不成立的。

两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the f irst is visible to and ordered before the second)。

时间先后顺序与先行发生原则之间基本没有因果关系,所以我们衡量并发安全问题的时候不要受时间顺序的干扰,一切必须以先行发生原则为准。

i = 1;       //线程A执行
j = i ;      //线程B执行

j 是否等于1呢?假定线程A的操作(i = 1)happens-before线程B的操作(j = i)。
那么可以确定线程B执行后j = 1 一定成立。
如果他们不存在happens-before原则,那么j = 1 不一定成立。

(即使代码是先执行j=1,然后执行j=i,也不一定j=1,主要看是否符合happens-before)


下面是Java内存模型下一些“天然的”先行发生关系

·程序次序规则(Program Order Rule):在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。注意,这里说的是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。

·管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后。

·volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后。

·线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。

·线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。

·线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread::interrupted()方法检测到是否有中断发生。

·对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

·传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操

基于volatile的happens-before

在这里插入图片描述
1)根据程序次序规则,1 happens-before 2;3 happens-before 4。

2)根据volatile规则,2 happens-before 3。

3)根据happens-before的传递性规则,1 happens-before 4。

6.java线程状态切换

在这里插入图片描述
·阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
在这里插入图片描述

7. 基于volatile的内存语义

  • 写语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
  • 读语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

volatile内存语义的实现(禁止指令重排序的实现)

❑ 在每个volatile写操作的前面插入一个StoreStore屏障。
❑ 在每个volatile写操作的后面插入一个StoreLoad屏障。
❑ 在每个volatile读操作的后面插入一个LoadLoad屏障。
❑ 在每个volatile读操作的后面插入一个LoadStore屏障。

在这里插入图片描述
例子:
在这里插入图片描述
在这里插入图片描述

发布了37 篇原创文章 · 获赞 6 · 访问量 4637

猜你喜欢

转载自blog.csdn.net/littlewhitevg/article/details/105568194