java内存模型的概念
jvm定义一种内存模型来屏蔽各种硬件和操作系统内存模型的差异,以实现java的跨平台特性,java是一种多线程语言,要想理解并正确的使用java进行并发编程,应该首先了解java的内存模型。
工作内存和主内存
java内存模型规定所有的共享变量都存储在主内存中,jvm中堆内存是线程所共有的,实例域,静态域和数组元素都存储在堆内存中,所以共享变量是指实例域,静态域和数组元素,注意此处所说的主内存与堆空间是没有关系的,因为这两个概念不是同一个层次的划分。每个线程都拥有自己的工作内存,线程只能与工作内存交互,不能直接访问主内存,本地内存中存储了该线程读写共享变量的副本。
内存交互协议
主内存与工作内存是如何交互的,也就是一个变量是如何从主内存复制工作内存和工作内存同步回主内存?java内存模型定义了以下规则,同时jvm保证以下操作是原子的。
1.lock:作用于主内存的变量,它把一个变量标识为一条线程所独占,其他要访问该变量的线程在unlock之前只能等待。
2.unlock:作用于主内存中的变量,他把一个处于lock状态的变量释放出来,可以被其他线程所访问和锁定。
3.read:作用于主内存中的变量,它把主内存中一个变量的值从主内存中传递到线程的工作内存中,为了后续的load操作。
4.load:作用于工作内存的变量,它把read读取的值,放入到工作内存变量副本中。
5.use:作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机需要使用变量的值的字节码指令时将会执行这个操作。
6.assign:作用于工作内存,它把一个从执行引擎接收到的值赋值给工作内存的变量。
7.store:作用于工作内存的变量,它把工作内存中的变量的值传递到主内存中,为了write操作使用。
8.write:作用于主内存的变量,它把store操作从工作内存中获得的值,赋值给主内存中的变量。
java内存模型规定了以上八种操作必须满足以下规则:
1.如果要把一个变量从工作内存同步回主内存中,必须顺序的执行store,write操作,把一个变量从主内存复制到工作内存必须顺序的执行read,load操作。
2.不允许read,load或者store,write单独出现,也就是不允许从主内存读取的变量工作内存不接受,不允许从工作内存同步回主内存的变量主内存不接受。
3.变量在工作内存中改变值之后,必须同步回主内存,注意所有的变量都必须满足这个条件,只是volatile型变量必须立即刷新回主内存,而且当使用时也必须从主内存获取,这就使得volatile具有内存可见性。
4.所有的变量必须在主内存中产生,不允许工作内存直接使用未经初始化的变量,这里的变量还是指共享变量。
5.一个变量在同一时刻只允许一个线程对其进行lock操作,但lock操作可以被同一条线程执行多次,多次lock后,只要进行相同次数的unlock操作即可解锁,synchronized关键字所指定的同步代码块就是对应字节码指令monitorenter和monitorexit,这两个字节码就是对应lock操作和unlock操作。
6.在对一个变量执行lock操作之前,必须清空工作内存中这个变量的值,也就是使用这个变量的值必须重新从主内存中获取,这个和volatile型变量的其中一个语义相同。
7.在对一个变量执行unlock操作之前,必须先把变量同步回主内存。第6条和第7条结合起来,保证了lock和unlock操作的变量具有可见性,对于到代码中,也就是synchronized代码块就有可见性。
java内存模型特征
java内存模型主要围绕着在并发编程中如何处理原子性,可见性和有序性三个特征来建立的。
1.原子性:就是指不可再分,是最小的操作,并发下是安全的,java内存模型保证read,load,use,assign,store,write操作和被lock和unlock所指定的代码块,变量。
2.可见性:是指一个主内存中的变量在工作内存中修改后,其他工作内存立即得知这个修改。
3.有序性:在程序执行过程中,编译器和处理器为了提高性能,经常会对指令进行重排序,这种重排序保证一个线程内的程序执行结果不会改变,但是在并发编程中,这种重排序可能会使得程序的结果发生改变。
三个关键字volatile,synchronized,final
1.volatile关键字
两种语义:
第一种:保证volatile所修饰的变量的可见性,即修改被立即得知。
第二种:禁止指令重排序,也就是说在volatile之前的代码不会放到volatile之后执行,在volatile之后的代码也不会放到volatile之前执行,相当于内存屏障。因此并发编程中有时候可以使用volatile关键字保证一个变量的线程安全,在以下这样情况就可以使用volatile关键字。
2.synchronized关键字
synchronized既可以作用于对象还可以作用于代码块和方法,synchronized的实现原理是任何对象都有一个monitor与之关联,当monitor被劫持后将处于锁定状态,而synchronized关键字对应的是monitorenter和monitorexit指令,当线程执行到monitorenter指令时会尝试获取对象的monitor的所有权,也就是获取对象的锁。
根据java内存模型的以下五条可以很容易得出synchronized满足原子性,可见性和有序性。1.lock:作用于主内存的变量,它把一个变量标识为一条线程所独占,其他要访问该变量的线程在unlock之前只能等待。
2.unlock:作用于主内存中的变量,他把一个处于lock状态的变量释放出来,可以被其他线程所访问和锁定。
3.一个变量在同一时刻只允许一个线程对其进行lock操作,但lock操作可以被同一条线程执行多次,多次lock后,只要进行相同次数的unlock操作即可解锁,synchronized关键字所指定的同步代码块就是对应字节码指令monitorenter和monitorexit,这两个字节码就是对应lock操作和unlock操作。
4.在对一个变量执行lock操作之前,必须清空工作内存中这个变量的值,也就是使用这个变量的值必须重新从主内存中获取,这个和volatile型变量的其中一个语义相同。
5.在对一个变量执行unlock操作之前,必须先把变量同步回主内存。
3.final关键字
final域的内存语义:
1.写final域的内存语义:禁止把final域的写重排序到构造函数之外,也就是说会在final域的写之后,构造函数return之前,插入一个内存屏障。
2.读final域的内存语义: 初次读该对象的引用和初次读该对象中的final域这连个操作之间不能重排序。
happen-before原则
happen-before原则用来阐述内存可见性,是来判断数据是否存在竞争,线程是否安全的主要原则,如果A操作happen-before与操作B,是指A产生的影响能被B观察到,这两个操作可以在一个线程中也可以在两个线程中。
happen-before定义:
happen—before用来指定两个操作的执行顺序,如果一个操作happen-before于另一个操作,那么第一个操作的执行结果对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前;如果两个操作之间存在happen-before关系,并不意味着必须按照happen-before的顺序来执行,只要结果是和按照happen—before的执行顺序相同就可以,这个和单线程的指令重排序是相同的意思。
happen-before规则
1.程序顺序规则:一个线程中的操作happen-before于后续的任意操作
2.监视锁规则:对一个锁的解锁,happen-before于随后对这个锁的加锁。
3.volatile变量规则:对一个volatile变量的写操作happen-before于后续对这个变量的读。
4.传递性:A happen-before B, B happen-before C,则A happen-before C.
5.start()规则:对于一个线程的start()操作happen—before于线程的后续任意操作.
6.join()规则:如果线程A执行ThreadB.join()并成功返回,那么线程B中的任意操作happen-before于线程A从ThreadB返回后的任意操作。
参考书籍
《深入理解java虚拟机》
《Java并发编程的艺术》