JMM即为JAVA 内存模型(java memory model)。由于在不一样的硬件生产商和不一样的操做系统下,内存的访问逻辑有必定的差别,结果就是当你的代码在某个系统环境下运行良好,而且线程安全,可是换了个系统就出现各类问题。Java内存模型,就是为了屏蔽系统和硬件的差别,让一套代码在不一样平台下能到达相同的访问结果。JMM从java 5开始的JSR-133发布后,已经成熟和完善起来。html
JMM规定了内存主要划分为主内存和工做内存两种。此处的主内存和工做内存跟JVM内存划分(堆、栈、方法区)是在不一样的层次上进行的,若是非要对应起来,主内存对应的是Java堆中的对象实例部分,工做内存对应的是栈中的部分区域,从更底层的来讲,主内存对应的是硬件的物理内存,工做内存对应的是寄存器和高速缓存。java
JVM在设计时候考虑到,若是JAVA线程每次读取和写入变量都直接操做主内存,对性能影响比较大,因此每条线程拥有各自的工做内存,工做内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工做内存中操做,而不能直接去操做主内存中的变量。可是这样就会出现一个问题,当一个线程修改了本身工做内存中变量,对其余线程是不可见的,会致使线程不安全的问题。由于JMM制定了一套标准来保证开发者在编写多线程程序的时候,可以控制何时内存会被同步给其余线程。程序员
内存交互操做有8种,虚拟机实现必须保证每个操做都是原子的,不可在分的(对于double和long类型的变量来讲,load、store、read和write操做在某些平台上容许例外)缓存
JMM对这八种指令的使用,制定了以下规则:安全
JMM对这八种操做规则和对volatile的一些特殊规则就能肯定哪里操做是线程安全,哪些操做是线程不安全的了。可是这些规则实在复杂,很难在实践中直接分析。因此通常咱们也不会经过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。多线程
原子性:例如上面八项操做,在操做系统里面是不可分割的单元。被synchronized关键字或其余锁包裹起来的操做也能够认为是原子的。从一个线程观察另一个线程的时候,看到的都是一个个原子性的操做。并发
1 synchronized (this) { 2 a=1; 3 b=2; 4 }
例如一个线程观察另一个线程执行上面的代码,只能看到a、b都被赋值成功结果,或者a、b都还没有被赋值的结果。app
可见性:每一个工做线程都有本身的工做内存,因此当某个线程修改完某个变量以后,在其余的线程中,未必能观察到该变量已经被修改。volatile关键字要求被修改以后的变量要求当即更新到主内存,每次使用前从主内存处进行读取。所以volatile能够保证可见性。除了volatile之外,synchronized和final也能实现可见性。synchronized保证unlock以前必须先把变量刷新回主内存。final修饰的字段在构造器中一旦完成初始化,而且构造器没有this逸出,那么其余线程就能看到final字段的值。
框架
有序性:java的有序性跟线程相关。若是在线程内部观察,会发现当前线程的一切操做都是有序的。若是在线程的外部来观察的话,会发现线程的全部操做都是无序的。由于JMM的工做内存和主内存之间存在延迟,并且java会对一些指令进行从新排序。volatile和synchronized能够保证程序的有序性,不少程序员只理解这两个关键字的执行互斥,而没有很好的理解到volatile和synchronized也能保证指令不进行重排序。工具
被final修饰的变量,相比普通变量,内存语义有一些不一样。具体以下:
1 public class FinalConstructor { 2 3 final int a; 4 5 int b; 6 7 static FinalConstructor finalConstructor; 8 9 public FinalConstructor() { 10 a = 1; 11 b = 2; 12 } 13 14 public static void write() { 15 finalConstructor = new FinalConstructor(); 16 } 17 18 public static void read() { 19 FinalConstructor constructor = finalConstructor; 20 int A = constructor.a; 21 int B = constructor.b; 22 } 23 }
假设如今有线程A执行FinalConstructor.write()方法,线程B执行FinalConstructor.read()方法。
对应上述的Final的第一条规则,由于JMM禁止把Final域的写重排序到构造器的外部,而对普通变量没有这种限制,因此变量A=1,而变量B可能会等于2(构造完成),也有可能等于0(第11行代码被重排序到构造器的外部)。
对应上述的Final的第二条规则,若是constructor的引用不为null,A必然为1,要么constructor为null,抛出空指针异常。保证读final域以前,必定会先读该对象的引用。可是普通对象就没有这种规则。
(上述的Final规则反复测试,遗憾的是我并无能模拟出来普通变量不能正常构造的结果)
在常规的开发中,若是咱们经过上述规则来分析一个并发程序是否安全,估计脑袋会很疼。由于更多时候,咱们是分析一个并发程序是否安全,其实都依赖Happen-Before原则进行分析。Happen-Before被翻译成先行发生原则,意思就是当A操做先行发生于B操做,则在发生B操做的时候,操做A产生的影响能被B观察到,“影响”包括修改了内存中的共享变量的值、发送了消息、调用了方法等。
Happen-Before的规则有如下几条
以上就是Happen-Before中的规则。经过这些条件的断定,仍然很难判断一个线程是否能安全执行,毕竟在咱们的时候线程安全多数依赖于工具类的安全性来保证。想提升本身对线程是否安全的判断能力,必然须要理解所使用的框架或者工具的实现,并积累线程安全的经验。