上篇中,我们用 synchronized 将 var++ 变成了原子操作,那 可见性如何保持?
要了解可见性,我们得先来了解一下 Java 内存模型。
Java 内存模型(Java Memory Model,简称 JMM)描述了 Java 程序中各种变量(线程之间的共享变量)的访问规则,以及在 JVM 中将变量存储到内存→从内存中读取变量的底层细节。
要知道,所有的变量都是存储在主内存中的,每个线程会有自己独立的工作内存,里面保存了该线程使用到的变量副本(主内存中变量的一个拷贝)
Java锁保证可见性的具体实现
Happens-before规则
从JDK 5开始,JSR-133定义了新的内存模型,内存模型描述了多线程代码中的哪些行为是合法的,以及线程间如何通过内存进行交互。
新的内存模型语义在内存操作(读取字段,写入字段,加锁,解锁)和其他线程操作上创建了一些偏序规则,这些规则又叫作Happens-before规则。它的含义是当一个动作happens before另一个动作,这意味着第一个动作被保证在第二个动作之前被执行并且结果对其可见。我们利用Happens-before规则来解释Java锁到底如何保证了可见性。
Java内存模型一共定义了八条Happens-before规则,和Java锁相关的有以下两条:
内置锁的释放锁操作发生在该锁随后的加锁操作之前
一个volatile变量的写操作发生在这个volatile变量随后的读操作之前
synchronized提供的可见性
synchronized有两种用法,一种可以用来修饰方法,另外一种可以用来修饰代码块。我们以synchronized代码块为例:
synchronized(SomeObject) {
// code
}
因为synchronized代码块是互斥访问的,只有一个线程释放了锁,另一个线程才能进入代码块中执行。
由上述Happens-before规则第一条:
内置锁的释放锁操作发生在该锁随后的加锁操作之前
假设当线程a释放锁后,线程b拿到了锁并且开始执行代码块中的代码时,线程b必然能够看到线程a看到的所有结果,所以synchronized能够保证线程间数据的可见性。
volatile关键字的可见性
volatile boolean ok = false
int a = 0
// Thread a
a = 1
ok = true
// Thread b
if (ok) {
// 确保a的值为1
System.out.println(a)
}