基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程 (2)

基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程

Java内存模型

通过CPU缓存和JVM工作模式的介绍,是为了引入Java内存模型的概念。Java内存模型(Java Memory Mode, JMM)定义了JVM如何与计算机的主存进行工作,理解JMM对正确理解Java多线程开发是十分重要的。JMM模型如下图所示:

基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程

Java内存模型的工作逻辑,与上面介绍到的CPU缓存一致性工作逻辑十分相似,其关于多线程的工作要点如下:

1. 共享变量存储于主内存中,每个线程都可以访问。

2. 每个线程都有私有的工作内存,或称本地内存。这只是个逻辑概念,其实质是涵盖了寄存器、缓存、编译器优化和硬件等。

3. 共享变量只以副本的形式,存储在本地内存中。

4. 线程不能直接操作主内存,只有操作了本地内存中的副本,才能刷新到主内存中。

5. 每个线程也不能操作其它线程的私有的本地内存

Java线程安全的实现

Java并发编程安全需要具备的三大特性:原子性、可见性和有序性。下面将介绍,基于JMM模型和Java线程安全的实现方式,是如何确保三大特性的。

原子性

在Java并发编程中,简单的读取和赋值操作是原子性的,但是多个原子操作并在一起就不是了,比如将一个变量赋值给另外一个变量的操作。

JMM只保证了简单读取和赋值的原子性。因此,并发编程中需要用到synchronized实现同步,或者使用Lock接口的实现类加锁;对于基本数据类型如int的自增操作,也可以使用JUC包下的java.util.concurrent.atomic.*包下的原子类型。而volatile修饰的变量,不具备原子性。

可见性

基于JMM模型,对于线程读取共享变量:首次只要从主内存读取到工作内存,以后都在工作内存中读取即可;对于修改共享变量,新值先更新在工作内存中,再刷新到主存中。但什么时候刷新是不确定的。因此,Java并发编程中,要确保共享变量在多线程中同步更新,可以采取如下方式:

通过synchronized关键字同步,可以确保在锁释放之前,对变量的修改刷新到主内存中;

通过Lock接口实现类实现同步,同样可以在锁unlock之前,把修改刷新到主内存中;

使用volatile关键字,当某线程修改了工作内存中的共享变量副本,会直接刷新主存中的值,并且其它线程会立刻收到本地内存中共享变量副本失效的信息,从而及时从主内存中更新值。

有序性

在JMM模型中,为了充分利用硬件性能,编译器和指令器有可能会对程序指令进行重排序。单线程下,这不会有什么问题,但多线程下则可能带来意想不到的状况。

关于并发编程的有序性,JMM基于一套原生Happens-before原则,来确保了多线程下一定程度的有序性。具体说来:

程序次序规则:即便发生了重排序,在一个线程内最终的运行结果会与程序编写顺序的结果一致。

锁定规则:先unlock再lock。即一个锁是锁定状态,需要先解锁才能再加锁。

volatile规则:如果一个线程对volatile变量读,另一个线程对该变量写,那么写操作一定发生在读操作之前。

传递规则:如果操作A先于B,B先于C,那么A肯定先于C。

线程启动规则:线程的start方法先于其它操作。

线程中断规则:必须是先有interrupt()方法调用,才有中断信号的捕获。

线程终结规则:线程的所有操作都必须先于线程死亡。

对象终结规则:一个对象的初始化先于对象GC之前。

此外,在并发编程中,比较常用的是使用synchronized关键字和Lock接口同步,或者volatile关键字,来确保多线程下的有序性。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywddj.html