最近看了各种关于java线程的文章, 但我觉得它们对java线程的工作内存都没有讲清楚, 并且非常的误导人. 大多数文章都直接丢了张图说每个线程里面有块空间叫工作内存, 但实际上呢, 按照java虚拟机规范的说法, 每个线程都有自己私有的PC计数器和一个虚拟机栈, 根本就没有所谓的工作内存. 继续深入java虚拟机的实现, 会发现这个大多数文章想说的工作内存其实指的是栈帧中的局部变量表, 其中会保存传入参数, this指针和局部变量. 当新开一条线程后, jvm会初始化这条线程的PC和虚拟机栈, 栈内的每一个栈帧又分为局部变量表, 操作数栈和动态链接. 操作数栈相当于CPU中的寄存器, 用来保存指令的参数, 而局部变量表则是存放field的地方, 只是不同的栈帧有不同的变量.
其实在学习过jvm的工作原理之后, 很多问题就都迎刃而解了, 而且也会不自主的去思考些深入的问题. 比如说了解了工作内存是栈帧这样的观念之后, 就会发现在调用方法之后是会有压栈的啊, 那么线程总的局部变量表(也就是"工作内存")是什么时候和堆进行同步的呢? synchonized关键词和volatile关键词是在方法调用的时候实现的呢还是在其他的什么时候, 是怎么触发的. 这些都值得细细研究.
先来谈谈可见性, 可见性分为两点: 1. 线程修改后能够及时的从栈帧中刷新到堆里去. 2.其他线程能及时的从堆里获得最新的值. 首先需要说明的是可见性是只有基础类型才需要保证的, 因为引用类型的值保存在堆里, 这是对所有线程都可见的. 而基础类型会复制一份过来保存到栈帧的局部变量表, 需要用时直接丢进操作数栈使用, 这些数据对其他线程不见. 其次要说明的是即使没有synchronized和volatile关键词, 编译器可能也会帮你把堆里的值和栈帧的值同步, 因此有时可能不加关键词而线程之间依然保持可见的现象, 但为了保险起见至少还是加个volatile吧.
synchonized可以实现原子性和可见性, 可见性是通过在加锁和解锁时对两块内存进行同步来实现的. 具体是怎么做的呢? 按照RednaxelaFX大大的说法, synchronized其实就是在block前后加上monitorenter和monitorexitvolatile关键词. volatile则是在每次读的过程中强制从堆中读, 每次写的过程中写回到堆中. volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.