文章已同步到GitHub开源项目:JVM底层原理分析Java内存模型JVM虚拟机规范曾试图定义一个Java内存模型来屏蔽各种硬件的内存访问差异和操作系统,使得Java程序在各种平台上都能达到一致的内存访问效果。然而,定义这样一套内存模型并不容易。这个模型必须足够严谨,这样Java的并发内存访问操作才不会出现歧义。但也必须足够松散,这样虚拟机的具体实现才能有空闲空间来发挥各种硬件优势。经过长时间的验证和补偿,Java内存模型在JDK1.5(实现JSR133规范)之后终于成熟了。主存和工作内存Java内存模型规定所有的变量都存放在主存(MainMemory)中,每个线程都有自己的工作内存(WorkMemory)。工作内存存储线程使用的变量。主内存拷贝,线程对变量的读写操作必须在工作内存中进行。而不是直接访问主存中的数据。不同的线程不能互相读写对方的工作内存,线程之间的变量必须通过主内存传递。主存和工作内存的交互Java内存模型定义了以下8个操作(每个操作都是原子的,不可分割的)lockLock:作用于主存,将一个变量标记为线程独占状态mainmemory,释放一个线程独占状态的变量readread:从主存读取数据到工作内存,方便后续的load操作使用:将工作内存中的变量传递给执行引擎当虚拟机遇到需要使用变量值的字节码时,执行这个操作assign赋值:将执行引擎中的值赋给内存中的工作变量。当虚拟机遇到赋值操作时,执行这个操作storestorage:将工作内存的值传送到主内存,方便后续的写操作writewrite:将storestorage操作中从工作内存中获取的变量写入mainmemory内存中的例子:如果要从主存中拷贝一个变量到工作内存中,那么依次执行读操作,如果要从工作内存中写一个变量到主存中,那么执行store顺序操作。writeoperation以上8个操作必须满足以下规则:read和load、store和write操作中的一个不能单独出现。也就是说,一个变量不允许从主存中读取而工作存不接受,也不允许从工作存发起回写请求而主存不接受。一个线程不允许丢弃它最新的赋值操作,即变量在工作内存中发生变化后必须同步到主内存中。一个线程不允许无故(没有发生assign操作)将数据从工作内存同步回主内存。一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未初始化(加载或赋值)的变量。也就是说,在对一个变量实现use和store操作之前,必须先进行assign和load操作。一个变量在同一时刻只能被一个线程加锁,但是加锁操作可以被同一个线程多次执行。多次执行锁后,只有执行相同次数的解锁操作后,变量才会被解锁。lock和unlock必须成对出现。如果对一个变量进行加锁操作,工作内存中该变量的值将被清空。执行引擎在使用这个变量之前,需要重新执行load或者assign操作来初始化这个变量的值。如果一个变量之前没有被使用过lock操作加锁,则不允许对其进行解锁操作;也不允许解锁被其他线程锁定的变量。在对变量执行解锁操作之前,变量必须同步到主存(执行存储和写入操作)。volatile的特殊规则volatile可以说是Java虚拟机提供的最轻量级的同步机制。但要正确完整地理解并不容易。Java内存模型规定,当一个变量定义为volatile时,表示线程工作内存无效,对该值的读写操作将直接作用于主内存,因此对所有线程具有即时可见性.保证这个变量对所有线程立即可见当修改变量的值时,新值立即为其他线程所知。普通变量做不到这一点,因为普通变量的值在线程之间传递是通过进入主存来完成的。例如,当线程A写回一个变量时,线程B只能看到A写回后主存中的新值。在A写回主存的过程中,B仍然读取到旧值。但这并不能推断基于volatile变量的操作在并发下是安全的,因为Java中的操作符不是原子的。这使得volatile变量并发操作是不安全的。通过代码验证volatile变量是否在并发下运行是不安全的。首先我们创建20个线程,每个线程对volatile变量进行1000次自增操作。/***@Author:写bug的小杜【email@shaoxiongdu.cn】*@Time:2021/07/31*@Description:通过代码验证【并发下volatile变量操作不安全】*/publicclassVolatileTest{//volatile修饰计数privatestaticvolatileintcount=0;//计数自增方法publicstaticvoidincrement(){count++;}publicstaticvoidmain(String[]args){//incrementcountby1000本次操作的runnable接口Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){System.out.println(Thread.currentThread().getName()+"线程开始递增计数");对于(inti=0;i<1000;i++){increment();}System.out.println(Thread.currentThread().getName()+"线程计数递增操作结束");}};//创建20个线程并启动for(inti=0;i<20;i++){Threadthread=newThread(runnable);thread.setName((i+1)+"编号线程");thread.start();}while(Thread.activeCount()>2){//主线程回到就绪状态Thread.yield();}System.out.println("所有线程结束,count="+count);}}如果这个程序在并发下是安全的,那么最后count的值一定是20*1000=20000;也就是说,如果运行结果是20000,volatile变量并发操作是安全的。通过多次运行程序,我们发现count的值总是小于20000,那么,这是为什么呢?我们对上面的代码进行反编译,然后分析increment方法的字节码指令。0getstatic#2
