Java内存模型
现在计算机普遍使用多处理器进行运算,并且为了解决计算机存储设备和处理器的运算速度之间巨大的差距,引入了高速缓存作为缓冲,
缓存虽然能极大的提高性能,但是随之带来的缓存一致性的问题,
例如,当多个处理器同时操作同一个内存地址,可能会导致各自的缓存数据不一致,由此产生冲突问题,
内存模型就是定义一套充分必要的规范,这些规范使得其他处理器对内存的写操作对当前处理器可见,或者当前处理器的写操作对其他处理器可见。
类似物理上的计算机系统,Java虚拟机规范中也定义了一种Java内存模型,即Java Memory Model(JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果。
现在最新的Java内存模型规范是JSR-133,即Java内存模型与线程规范。
这套规范包含:
线程之间如何通过内存通信;
线程之间通过什么方式通信才合法,才能得到期望的结果。
线程间通信
线程间通信的方式有很多种,Windows下进程间通信及数据共享,这里只考察共享内存的方式。
对于Java的并发编程,线程间采用的是共享内存通信,
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。
volatile关键字和内存结构
先看一下Java内存模型对应的内存结构:
线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
本地内存是一个抽象的概念,包括缓存、写缓冲区、寄存器等,
一个线程对 volatile 变量的写一定对之后对这个变量的读的线程可见,
即线程对 volatile 变量的读一定能看见在它之前最后一个线程对这个变量的写。
volatile包含以下语义:
(1)Java内存模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
(2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。
为了实现这些语义,Java 规定,(1)当一个线程要使用共享内存中的 volatile 变量时,如图中的变量a,它会直接从主内存中读取,而不使用自己本地内存中的副本。(2)当一个线程对一个 volatile 变量进行写时,它会将这个共享变量的值刷新到共享内存中。
volatile 变量保证的是一个线程对它的写会立即刷新到主内存中,并置其它线程的副本为无效,它并不保证对 volatile 变量的操作都是具有原子性的。
volatile 变量的写可以被之后其他线程的读看到,因此我们可以利用它进行线程间的通信。如
1 2 3 4 5 6 7 |
|
线程A执行set()后,线程B执行get(),相当于线程A向线程B发送了消息。
volatile的语义,其实是告诉处理器,
不要将我放入工作内存,请直接在主存操作,多线程在访问该变量时,都将直接操作主存,这从本质上,做到了变量共享。
>>synchronized关键字
synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,没有的话,直接运行。
它包括两种用法:synchronized 方法和 synchronized 块。
final关键字
final关键字可以作用于变量、方法和类,我们这里只看final 变量。
final变量的特殊之处在于,final 变量一经初始化,就不能改变其值。
这里的值对于一个对象或者数组来说指的是这个对象或者数组的引用地址。因此,一个线程定义了一个final变量之后,其他任意线程都拿到这个变量。
但有一点需要注意的是,当这个final变量为对象或者数组时,
虽然我们不能讲这个变量赋值为其他对象或者数组,但是我们可以改变对象的域或者数组中的元素。
线程对这个对象变量的域或者数据的元素的改变不具有线程可见性。
Java内存模型对final有不用的重排序规则,final引用不能从构造函数内“逸出”。