内存模型
JMM(java memory model)java内存模型规定java中所有的变量(指静态变量,常量等线程共享的变量,不包括局部变量等私有的变量)均存放于主内存中,每个线程则有各自的工作内存,工作内存中的变量是主内存的副本,线程只能对工作内存中的变量进行直接操作,并且线程之间是不能互相访问对方的工作内存的,只能依靠主内存进行通信。
Java 内存模型对主内存与工作内存之间的具体交互协议定义了八种操作,具体如下:
-
lock(锁定):作用于主内存变量,把一个变量标识为一条线程独占状态。
-
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
-
read(读取):作用于主内存变量,把一个变量从主内存传输到线程的工作内存中,以便随后的 load 动作使用。
-
load(载入):作用于工作内存变量,把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
-
use(使用):作用于工作内存变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量值的字节码指令时执行此操作。
-
assign(赋值):作用于工作内存变量,把一个从执行引擎接收的值赋值给工作内存的变量,每当虚拟机遇到一个需要给变量进行赋值的字节码指令时执行此操作。
-
store(存储):作用于工作内存变量,把工作内存中一个变量的值传递到主内存中,以便后续 write 操作。
-
write(写入):作用于主内存变量,把 store 操作从工作内存中得到的值放入主内存变量中。
如果要把一个变量从主内存复制到工作内存就必须按顺序执行 read 和 load 操作,从工作内存同步回主内存就必须顺序执行 store 和 write 操作,但是 JVM 只要求了操作的顺序而没有要求上述操作必须保证连续性,所以实质执行中 read 和 load 间及 store 和 write 间是可以插入其他指令的。
指令重排序
指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序.
sychronized和volatile来保证程序执行过程中的原子性, 有序性, 可见性。但是仅靠它们来保证,则代码会时分繁琐。
因此JMM提供了Happen-Before规则来约束数据之间是否存在竞争。
happens-before原则:
Java内存模型中定义的两项操作之间的次序关系,如果说操作A先行发生于操作B,操作A产生的影响能被操作B观察到,“影响”包含了修改了内存中共享变量的值、发送了消息、调用了方法等。
下面是Java内存模型下一些”天然的“happens-before关系,这些happens-before关系无须任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随意地重排序。
a.程序次序规则(Pragram Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环结构。
b.管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于对同一个锁的lock操作的后面。lock先于unlock
c.volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读取操作。volatile变量写先于读。
d.线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
e.线程终于规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值等作段检测到线程已经终止执行。
f.线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生。中断先于中断检测
g.对象终结规则(Finalizer Rule):一个对象初始化完成(构造方法执行完成)先行发生于它的finalize()方法的开始。
f.传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。