简介Java内存模型的主要目的是定义程序中各种变量的访问规则,即关注将变量的值写入内存并取内存的底层细节它超出了虚拟机的内存。这里的变量指的是可以共享的变量,比如实例字段、静态字段、组成数组的元素等。主存和工作内存Java内存模型规定所有的变量都存储在主存(MainMemory)中,每个线程都有自己的工作内存(WorkingMemory)。线程的工作内存存储线程使用的变量的主内存副本。线程只能在自己的工作内存中读写变量,不能直接读写主内存中的数据。不同的线程不能直接访问彼此工作内存中的变量,线程间变量数据的传递必须通过主内存作为中介来完成。线程、工作内存和主存之间的交互关系如图所示:内存之间的交互是关于主存和工作内存之间的交互协议,即如何将一个变量从主存复制到工作内存,以及如何同步从工作内存到主内存内存的实现细节,Java内存模型定义了八个操作,每个操作都是原子的,不可分割的,如下:锁(lock):作用于主内存中的变量,它把一个变量标记为线程独占状态的解锁(unlock):作用于主存中的变量,它释放一个标记为锁定状态的变量读(read):作用于主存中的变量,它释放一个从主存中调用的变量加载到线程的工作内存中,以供后续加载操作加载(load):作用于线程工作内存的变量,将读操作从主内存读取的变量加载到工作内存的变量副本use(use):作用于线程工作内存中的变量,将工作内存中变量的值传递给执行引擎,当虚拟机遇到需要使用该变量的字节码指令时,就会执行这个操作assign(赋值)):作用于线程工作内存中的一个变量,将从执行引擎接收到的一个值赋值给工作内存中的一个变量,当线程遇到执行引擎需要赋值的字节码指令时,就会执行这个操作store(存储):作用于线程的工作内存变量,将工作内存中的一个变量的值传送到主存,以便后续的写操作可以写入主存write(写):作用于mainmemory,将storeprocess传过来的变量的值写入主存原子性、可见性和有序性Java内存模型的定义和规则是围绕着如何在执行过程中保证原子性、可见性和有序性这三个特性而建立的多个线程并发执行。原子性(Atomicity)Java内存模型直接保证的原子变量操作包括:read、load、assign、use、store和write。基于此,基本数据类型(char、short、int、boolean)的访问、读写都是原子的(long、double的非原子协议除外)。如果应用场景需要更大范围的原子性保证,Java内存模型提供了lock和unlock操作来满足这种需求。虚拟机虽然不会直接给用户释放锁和解锁,但是它提供了更高层的字节码指令monitorenter和monitorexit隐式实现了这两个操作。这两条字节码指令在Java中体现出来的实现就是同步块的synchronized关键字。可见性(Visibility)可见性是指线程对工作内存中共享变量的值的修改立即对其他线程可见。Java的内存模型通过在变量修改后同步变量值到主存,并在读取变量前将变量值从主存刷新到工作内存来实现可见性。就是这样。普通变量和volatile的区别在于,volatile的特殊规则保证了新值可以立即同步到主存,每次使用前强制从主存刷新。所以volatile可以保证多线程运行时变量的可见性,而普通变量不保证这一点。除了volatile,synchronized和final也可以保证共享变量的可见性。其中,synchronized的可见性是通过在对变量执行unlock之前强制将变量的值同步到主存来实现的。final关键字的可见性是指一旦final修饰的变量在构造函数中被初始化,构造函数不会将“this”的引用传递出去(这个引用的转义会导致其他线程通过这个引用访问它)到“半初始化”对象),然后其他线程可以看到最终字段的值。顺序Java虚拟机的即时编译器会对代码指令进行重新排序和优化。普通变量只会保证在线程中的代码执行过程中,在依赖于这个变量结果的地方能够得到正确的结果,而不能保证变量赋值。操作顺序与程序代码的顺序相同。这种重排序优化在多线程运行下可能会导致意想不到的问题。用下面的伪代码来说明:MapconfigOptions;char[]configText;//这个变量需要声明为volatile变量volatilebooleaninitialized=false;//下面代码运行在线程A中//模拟读取配置信息,读取完成后初始化为trueconfigOptions=newHashMap<>();configText=readConfigFile(fileName);processConfigOptions(configText,configOptions);initialized=true;//下面的代码在线程B中执行while(!initialized){sleep();}//使用在线程A中初始化的配置信息doSomethingWithConfig();如果指令重排序,线程A中的initialized=true指令可能会在加载配置文件之前执行,那么线程B中就会出现使用未加载配置信息的错误。volatile关键字可以禁止指令重排序,即它不允许改变initialized=true原来在代码指令中的位置,从而保证代码按照预期的执行顺序执行,不会出现意想不到的问题。除了volatile关键字,synchronized还可以保证多线程环境下的顺序。synchronized的特性保证一次只能有一个线程执行,多个线程串行执行。每个解锁操作共享变量修改必须被强制刷新到主内存。Happends-Before原则在Java中只有volatile和synchronized来保证有序性,所以操作起来会比较繁琐。Java语言定义了“Happends-Before”原则,先发生的操作结果对后续操作可见,这些原理自然存在,不需要任何同步器的辅助,可以直接在代码中使用。具体规则如下:程序顺序规则:在一个线程中,按照控制流顺序,写在前面的操作先发生,写在后面的操作先发生。监听锁规则:先解锁操作,再对同一个锁进行后续操作volatile变量的锁操作规则:对volatile变量的修改操作先于对变量的读操作线程启动规则:线程的start()方法线程对象发生在线程的每一次操作之前线程终止规则:线程中的所有操作firstandBoperation,BoperationfirstandCoperation,thenAoperationoccursbeforeCoperation对于可见性的判断,我们可以直接使用上面的规则进行判断,也可以通过多个规则推导判断。
