在讲解java的内存模型之前,咱们先看看物理机上的内存模型。
硬件的缓存结构
正如上面所看到的,每一个处理器都会有自己独自的缓存,而且可能会有一层缓存时多个处理器是共享的,最底层一层的缓存连接着主存,相当于是主存的部分缓存数据。
Java的内存模型
上面的图虽然画的简陋了点,但也基本表明线程、工作内存、主内存的交互情况。Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。这里的变量指的是线程共享的变量,比如实例字段、静态字段、构成数组对象的元素,但不包括局部变量和方法参数。
下面主要介绍下内存间的交互操作和先行发生原则,因为我感觉这块是核心也是比较难以理解的,书上也给出了定义,但是真正在理解过程中还是很困难的,我的理解也不一定对,大家参考即可。
内存间交互操作
Java内存模型中定义个8个原子操作,通过这8中原子操作,实现主内存和工作内存之间的交互。
- lock(锁定):作用于主内存的变量,它把一个变量标识为一个线程独占的状态。
这个操作高级的抽象是java的字节码指令monitorenter,正如我们使用synchronized的同步方法或者同步块时,通过反编译,可以看到monitorenter这条字节码指令,一个线程加锁后,就会导致其他的线程访问同一个对象时进入阻塞状态。
- unlock(解锁):作用于主内存变量,它把一个处于锁定状态的变量释放出来,释放后的变量才能被其他线程锁定。
这个操作高级的抽象是java的字节码指令monitorexit,是和monitorenter成对出现的。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到一个线程的工作内存中,以便随后的load动作使用。
如果直接理解read操作,还好理解,但是如果将read和接下来的load对比起来来理解,发现反而不懂了。下面看看load的解释,然后回过头来看看两者的区别。
- load(载入):作用于工作内存中变量,它把从read操作从主内存得到的变量值放入到工作内存的变量副本中。
放入到工作内存的变量副本中?我的理解是read操作就是我们物理层面将主存的数据缓存到高速缓存中,而load操作需要结合java语言来看,read读取进缓存中的数据可以通过物理地址来分配给任何变量使用,而分配给工作内存中的变量副本就是其一项操作而已。
- use(使用):作用于工作内存的变量,它把工作内存中的一个变量传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时都会执行这个操作。
执行引擎?是不是就是处理器呀,将变量的值传给执行引擎应该就是将对应的值放到特定的寄存器中吧。
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到给一个变量赋值的字节码指令时将会执行这个操作。
像赋值操作,应该也是先把对应的值放到寄存器,然后再将寄存器的值回写到工作内存变量所对应的位置上。
- store(存储):作用于工作内存的变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作使用。
主内存?我知道内存是有一个内存控制器、还有内存行缓存区的概念,先写到主内存是指先把变量的值写到一个缓存区中???
- write(写入):作用于主内存的变量,它把store操作从工作内存得到的变量的值放入到主内存的变量中。
注:其实上面的很多原子操作我也懵懵懂懂的,只能根据自己的理解写了注释,如果大家好的见解,欢迎给出。
先行发生原则
什么是先行发生原则?
先行发生时Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能够被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等等。
Java内存模型下“天然”的先行发生关系
- 程序次序规则
在一个线程内,按照代码执行顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序。 - 管程锁定规则
一个unlock操作先行发生于后面对同一个锁的lock操作。这里强调的是同一个锁,而“后面”同样指的是时间上的先后顺序。 - volatile变量规则
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是时间上的先后顺序。 - 线程启动规则
Thread对象的start方法先行发生于此线程的每一个动作。 - 线程终止规则
线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。 - 线程中断规则
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。 - 对象终结规则
一个对象初始化完成(构造函数执行结束)先行发生于 它的finalize()方法的开始。 - 传递性
操作A先行发生于操作B,操作B先行发生于操作C,则操作A先行发生于操作C,满足传递性。