无论在什么编程语言里面,读取和写入都是咱们程序最广泛的操做,在单线程的程序里面咱们可能不关注线程的读写问题,可是一旦到多线程的环境下,读和写就会变得很是敏感。Java内存模型其实是定义了在多线程环境下使用读和写操做结果一致性的问题。这个模型在JDK5中经过JSR-133议案进行了修订。java
主要的缘由仍是在于方便程序员更加关注业务自己还不是底层细节,对程序员来讲理解操做系统的内存架构,CPU指令优化,JIT编译器优化是比较困难的一件事。程序员
在多核的服务器时代CPU通常都会拥有多级cache,为了提高其处理性能,好比在上篇文章提到过的L1,L2,L3级cache。这种服务器架构的问题主要在于程序里面的共享变量在横跨多个线程时候的可见性问题。对应到咱们写的程序里面就是一个线程写完的变量数据,对于其余线程是否可见。在上面文章中提到过每一个线程共享进程的主内存,,同时拥有本身的线程local cache,涉及到变量的读写和可见性问题,其实就是线程的local cache与主内存的数据是否一致的问题。在一个多线程累加同一个变量的程序里面,若是一个线程更新了本身local cache的数据,那么必须在更新完把local cache的数据flush到主内存,不然其余线程读取到的数据就有多是错误的,另外一方面其余线程知道主内存的数据可能会更新,那么就必须放弃本身local cache的数据,直接从主内存加载最新版本的数据用来累加,不然就会出现更新结果不正确的状况。(这部分知识如今理解不了,不要紧,后面的文章会慢慢梳理。)编程
为了方便你们理解重排序的概念,我先举个简单的例子:安全
public static void main(String []args){ int a=3; int c=4; int d=a+c; System.out.printLn(d); }
上面的代码咱们看到a变量是先声明的,c变量是后声明的,但在底层编译,或者JIT优化执行的时候,有可能c变量先被解析,而后才是a变量,这就叫指令重排序,目的是为了提升执行效率,固然指令重排序是有约定的,无论执行顺序如何变更(底层优化致使),在单线程中,它的最终结果必须是和代码顺序执行的结果是一致的。如上面的程序,a和c的位置能够互换,可是和d的顺序是不能变的,这就是它的约定,这个在后面的文章会解释。服务器
那么什么是指令重排序,通俗点讲就是:多线程
你看到的代码顺序,不必定是它的执行顺序。架构
上面说了,重排序只保证在单线程程序中,不影响最终结果的前提下容许JIT或者硬件指令作一些优化,可是在多线程程序中重排序是可能会致使一些问题的。并发
重排序和变量可见性问题是多线程编程里面的主要问题,Java内存模型主要描述了下面两种状况的的处理:app
(1)重排序是底层编译器优化的结果,因此在Java内存模型里面有一些 happens-before 规则来约束重排序,好比说若是先后两个变量有依赖关系如上面例子中的a和d那么它是不能被重排序的,不然一旦重排序,是会致使程序逻辑错误。框架
(2)对于共享数据的写操做,是无法经过happens-before关系来约束的,如上面说到的累加的例子,此时须要经过Java里面锁的机制来避免。
以下图:
同步代码块主要完成了两件事情:
(1)对于共享代码在任什么时候候只保证有一个线程能够操做,这保证了原子性。
(2)lock和unlock操做会触发当前线程flush本身的cache的数据到主内存中,这保证了可见性的问题。
在Java里面用volatie关键字修饰共享变量仅仅只保证可见性,仅仅适用于任什么时候候只有一个线程更新,多个线程读取的业务。因此若是有超过一个线程以上对变量进行修改,那么必须使用锁机制来处理。
因此请你们记住volatile只保证了可见性,不保证原子性。
在Java里面final关键字修饰的变量,仅仅会被初始化一次,后面是不能修改的。
JIT编译器对final的变量会进行优化,如基本类型String,Int,由于这里不存在修改的问题,那也就没有可见性的问题,因此final修饰基本类型变量在多线程的cache里面的是安全的,不须要和主内存有关联,也就不会有flush或者invalidate的状况。
这也是咱们常常说Java里面的String为何是安全的缘由,注意使用final修饰的集合框架如List,Set,Map,虽然内存地址不能变,可是里面的内容是能够变的,这里也是不安全的,这一点须要注意。
这也是为何有一些函数类型的编程语言如Scala里面严格的提供了不可变的集合框架和可变的集合框架,其目的就是为了更加有利于多线程编程。
最后记住final关键字和volatile关键字是不能修饰同一变量的,在IDE的编译器里面是直接会报错的。
若是在阅读以前不了解进程和线程在操做系统里面的关系与特色,我建议你先看看前面的两篇文章再阅读本文。本篇文章主要介绍了Java的内存模型相关内容,若是掌握和熟悉了这些知识,那么对于理解和开发并发编程将是很是有帮助的。