Java内存模型初探

JMM(Java Memory Model)简介

Java作为一种服务器端开发的强大语言,其多线程编程在实际开发中有着广泛的应用,提到Java多线程编程,要想保证项目在实际应用中满足线程安全,就要了解Java内存模型,懂得其原理,进行辅助开发。
众所周知,不同硬件以及不同操作系统的内存访问是由差异的。与C/C++这种直接使用物理硬件和操作系统的内存模型的语言不同,Java作为一种跨平台的编程语言,Java内存模型便是在Java虚拟机规范中定义的一种屏蔽掉不同硬件及不同操作系统的内存访问差异的机制。
据《深入理解Java虚拟机》一书中的表述,Java内存模型在JDK 1.5发布后已经成熟和完善起来了。

JMM初探

Java内存的划分与规定

JMM中,主要将内存划分为两部分:主内存和工作内存。

  • 主内存(线程共享):存储所有变量(包括实例字段静态字段以及构成数组对象的元素,不包括 局部变量方法参数
  • 工作内存(线程私有):存储被该线程使用到的变量的主内存副本拷贝
    在线程工作中,其所有对变量的操作均是在工作内存中完成的,线程不能直接读写主内存中的变量,工作内存中的信息会定时同步回主内存。

工作内存与主内存的交互

Java内存模型中定义了八种 原子操作 用来进行主内存与工作内存的交互。下面将简单介绍这八种操作,主要包括其作用的对象,以及用途。具体内容还请详见《深入理解Java虚拟机》。这八种操作如下:

  • lock:该操作作用于 主内存 中的变量,作用是把一个变量标识为一条线程独占的状态
  • unlock:该操作作用于 主内存 中的变量,它将被独占锁定的变量释放,使其可以被其它线程锁定
  • read:该操作作用于 主内存 中的变量,作用是将一个变量的值从主内存传输到线程的工作内存中,供load操作使用
  • load:该操作作用于 工作内存 中的变量,作用是将read操作从主内存中得到的变量值放入工作内存的变量副本中
  • use:该操作作用于 工作内存 中的变量,作用是将工作内存中一个变量的值传递给执行引擎
  • assign:该操作作用于 工作内存 中的变量,作用是把一个从执行引擎接收到的值赋给工作内存中的一个变量
  • store:该操作作用于 工作内存 中的变量,作用是将一个变量的值从工作内存传输到主内存中,供write操作使用
  • write:该操作作用于 主内存 中的变量,作用是将store操作从工作内存中得到的变量值放入主内存的变量中

由以上八个操作列举可以看出,lock、unlock为一组,作用为对主内存的变量进行加锁解锁;use、assign为一组,作用为执行引擎与工作内存的读取、写入操作;而read、load、store、write这四个操作则是供主线程与工作线程进行数据的同步所用。
在Java内存模型的规定中,还规定了以上八种操作在进行基本操作时必须满足的基本规则,这些规则与八种基本操作共同构成了JMM的核心。对于具体的规则在《深入理解Java虚拟机》一书里已经有了很详细的讲解,在此不再赘述。

JMM的特性

Java内存模型(Java Memory Model)主要有三个特性:原子性、可见性与有序性。

原子性

上文介绍的八种操作均为原子操作,其在最基本层次保证了基本数据类型读写的原子性。同时,学习过Java语言的应该对 synchronized 这个关键字也不会陌生,对于被synchronized修饰的块内,操作也可以认为是原子的。这就保证了非基本数据类型的操作的原子性。(关于synchronized的实现原理,将在单独文章中进行介绍)

可见性

在上文的八种操作介绍中,我们知道,若想让两个线程共享变量,则需要在一个线程更新该变量后,将该变量同步回主内存,然后另一个线程在读取时,首先从主内存中获取进行更新,然后读取。这就保证了线程间的可见性。

volatile、final、synchronized实现可见性

  • Java中的 volatile 关键字的特殊规则保证了新值能够立刻同步到主内存,以及每次使用前都从主内存刷新,这便保证了多线程操作时变量的可见性
  • 由于JMM规定,在对一个变量执行unlock之前,必须将该变量同步回主内存,所以在synchronized的使用结束后,便可以保证主内存中的变量已经被更新,这也就保证了可见性
  • final修饰的对象,由于其在类加载完成过后一旦初始化完成,便可以被访问,故也可保证可见性

有序性

Java语言中,volatile关键字的其中一个语义便是禁止指令重排序,故可以保证有序性;synchronized关键字则通过其对象锁控制,控制同一时刻一个对象的对象锁只能被一个线程获取,来保证串行,保证有序。

总结

本文算是博主个人的一个读书笔记,初读第一遍这部分的时候觉得晦涩难懂,在处理过一些多线程场景之后,再回来阅读该部分,对JMM便有了一些初步的理解。JMM主要围绕着一组定义而展开,这些定义便是帮助我们判断线程安全的基础,有了这些知识基础,才能够更好的处理多线程环境下的编程。