在笔者的第一本书《Java高并发编程详解:多线程与架构设计》中详细分析了关键字volatile,无论是基本数据类型还是引用类型的变量,只要被volatile关键字修饰,从JMM(Java Memory Model)的角度分析,该变量就具备了有序性和可见性这两个语义特质,但是它还是无法保证原子性。那么,什么是原子性呢?原子性是指某个操作或者一系列操作要么都成功,要么都失败,不允许出现因中断而导致的部分成功或部分失败的情况。
比如,对int类型的加法操作就是原子性的,如x+1。但是我们在使用的过程中往往会将x+1的结果赋予另一个变量甚至是x变量本身,即进行x=x+1或者x++这样的操作,而这样的语句事实上是由若干个原子性的操作组合而来的,因此它们就不具备原子性。这样的语句的具体实现步骤如下。
1)将主内存中x的值读取到CPU Cache中。
2)对x进行加一运算。
3)将结果写回到CPU Cache中。
4)将x的值刷新到主内存中。
再比如,long类型的加法x+1的操作就不是原子性的。在Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowbeer、David Holmes、Doug Lea合著的《Java Concurrency in Practice》一书的Nonatomic 64-bit operations章节中提到过:“a 64-bit write operation is basically performed as two separate 32-bit operations. This behavior can result in indeterminate values being read in code and that lacks atomicity.”(一个64位写操作实际上将会被拆分为2个32位的操作,这一行为的直接后果将会导致最终的结果是不确定的并且缺少原子性的保证。)在Java虚拟机规范中同样也有类似的描述:“For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.”详见虚拟机官方网址,地址如下:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7
在JDK 1.5版本之前,为了确保在多线程下对某基本数据类型或者引用数据类型运算的原子性,必须依赖于关键字synchronized,但是自JDK 1.5版本以后这一情况发生了改变,JDK官方为开发者提供了原子类型的工具集,比如AtomicInteger、AtomicBoolean等,这些原子类型都是Lock-Free及线程安全的,开发者将不再为一个数据类型的自增运算而增加synchronized的同步操作。本章将为大家详细介绍Java的各种原子类型(实际上在Java推出原子工具集之前,很多第三方库也提供了类似的解决方案,比如Google的Guava,甚至于JDK自身的原子类工具集也是来自Doug Lea的个人项目)。
在本章乃至本书中关于性能基准测试的所有方式都将依赖于JMH(Java Micro benchmark Harness)这一基准测试工具,因此建议读者认真阅读JMH的相关章节,并且掌握如何使用JMH进行基准测试。