整装待发
注:文章总结自深入理解Java内存模型-系列-程晓明
JMM 通过控制主内存与每个线程的本地内存之间的交互,来为 java 程序员提供内存可见性保证
注:每个处理器上的写缓冲区,仅仅对它所在的处理器可见
此处说明了缓存一致性的重要
分类
解决重排序的办法
重排序
顺序一致性模型保证单线程内的操作会按程序的顺序执行,而 JMM 不保证单线程内的操作会按程序的顺序执行(比如下图正确同步的多线程程序在临界区内的重排序)
对于下面代码:
// 线程A执行writer
// 线程B执行reader
class SynchronizedExample {
int a = 0;
boolean flag = false;
public synchronized void writer() {
a = 1;
flag = true;
}
public synchronized void reader() {
if (flag) {
int i = a;
……
}
}
}
顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而 JMM 不保证所有线程能看到一致的操作执行顺序
JMM 不保证对 64 位的 long 型和 double 型变量的读 / 写操作具有原子性,而顺序一致性模型保证对所有的内存读 / 写操作都具有原子性
当 JVM 在32bit处理器上运行时,会把一个 64 位 long/ double 型变量的读 / 写操作拆分为两个 32 位的读 / 写操作来执行
先看一段volatile
程序
class VolatileFeaturesExample {
volatile long vl = 0L; // 使用 volatile 声明 64 位的 long 型变量
public void set(long l) {
vl = l; // 单个 volatile 变量的写
}
public void getAndIncrement () {
vl++; // 复合(多个)volatile 变量的读 / 写
}
public long get() {
return vl; // 单个 volatile 变量的读
}
}
此程序相当于(即对每一个调用该变量的方法都加锁同步):
class VolatileFeaturesExample {
long vl = 0L; // 64 位的 long 型普通变量
public synchronized void set(long l) { // 对单个的普通 变量的写用同一个监视器同步
vl = l;
}
public void getAndIncrement () { // 普通方法调用
long temp = get(); // 调用已同步的读方法
temp += 1L; // 普通写操作
set(temp); // 调用已同步的写方法
}
public synchronized long get() {
// 对单个的普通变量的读用同一个监视器同步
return vl;
}
}
原子性:监视器锁的语义决定了临界区代码的执行具有原子性。这意味着即使是 64 位的 long 型和 double 型变量,只要它是 volatile
变量,对该变量的读写就将具有原子性。但如果是多个volatile
或者类似于volatile++
这种复合操作,这些操作整体上不具有原子性
可见性:对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入
线程通信:
是否能重排序 | 第二个操作 | ||
---|---|---|---|
第一个操作 | 普通读 / 写 | volatile 读 | volatile 写 |
普通读 / 写 | NO | ||
volatile 读 | NO | NO | NO |
volatile 写 | NO | NO |
在生成字节码的时候会插入指令屏障
class VolatileBarrierExample {
int a;
volatile int v1 = 1;
volatile int v2 = 2;
void readAndWrite() {
int i = v1; // 第一个 volatile 读
int j = v2; // 第二个 volatile 读
a = i + j; // 普通写
v1 = i + 1; // 第一个 volatile 写
v2 = j * 2; // 第二个 volatile 写
}
… // 其他方法
}
锁是 java 并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息
看一段代码
class MonitorExample {
int a = 0;
//线程A调用writer
public synchronized void writer() { //1
a++; //2
} //3
//线程B调用reader
public synchronized void reader() { //4
int i = a; //5
……
} //6
}
假设A先于B执行,则A拿到锁之后执行1、2、3,然后释放锁,B拿到相同的锁,执行4、5、6
线程B获得同一个锁之后,A中的共享变量,即a,相对于B可见
线程通信
对于类ReentrantLock
来说
PS: 其中红线代表内部类
ReenTrantLock分为FairSync和NonfairSync
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
对于公平锁的获取来说,实现顺序为:
ReentrantLock : lock()
public void lock() {
sync.acquire(1);
}
AbstractQueuedSynchronizer : acquire(int arg) (该类为Sync的基类)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ReentrantLock.FairSync : tryAcquire(int acquires)
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取锁的开始,首先读 volatile 变量 state
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
可以看出,加锁的时候要先读出volatile的变量state
对于非公平锁的加锁来说,实现顺序为:
ReentrantLock : lock()
public void lock() {
sync.acquire(1);
}
AbstractQueuedSynchronizer : acquire(int arg) (该类为Sync的基类)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ReentrantLock.NofairSync : tryAcquire(int acquires)
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
ReentrantLock.Sync : nonfairTryAcquire(acquires)
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
对于公平锁或者非公平锁的释放来说,实现顺序为:
ReentrantLock : unlock()
public void unlock() {
sync.release(1);
}
Sync : release(int arg) (继承了它的基类AbstractQueuedSynchronizer : release(int arg))
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
Sync : tryRelease(int releases)
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 释放锁的最后,写 volatile 变量 state
setState(c);
return free;
}
可以看出释放锁后会写入state变量
final使得其所修饰的值不被改变
如下例子
public class FinalExample {
int i; // 普通变量
final int j; //final 变量
static FinalExample obj;
public void FinalExample () { // 构造函数
i = 1; // 写普通域
j = 2; // 写 final 域
}
public static void writer () { // 写线程 A 执行
obj = new FinalExample ();
}
public static void reader () { // 读线程 B 执行
FinalExample object = obj; // 读对象引用
int a = object.i; // 读普通域
int b = object.j; // 读 final 域
}
}
当A执行writer之后,B执行reader时
在构造函数内对一个 final 域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序
如果final是引用类型,那么在构造函数内对一个final应用的对象的成员域的写入,与随后在构造函数外把这个被构造函数对象的引用赋值给另一个应用变量,这两个操作之间不能重排序