充电时刻(happen-before八个原则)
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
- 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
- start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
- Join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
- 程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
- 对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。
推荐阅读: java 8大happen-before原则超全面详解
happen-before不能理解成在什么之前发生,它和时间没有任何关系,解释成“生效可见于” 更准确。即使出现了指令重排,只要符合happen-before规则,那么上文的结果就会对下文生效。
关于内存可见性的疑惑
一个简单的例子
private static int count;
public void incr() {
//加锁
while(count <= 100) {
count++;
}
//解锁
}
不知道大家有没有像我一样的疑惑,无论是使用哪种加锁方式,count变量并没有被volatile修饰,那么对count变量的修改是如何保证内存可见性的呢?
好像对这件事是如此的理所应当,就像太阳每天从东方升起,不觉得这有什么好质疑的。
lock的内存可见性
以ReentrantLock公平锁为例,我们看看获取锁 & 释放锁的关键代码
private volatile int state;
protected final int getState() {
return state;
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//重要
int c = getState(); //line 1
//...竞争锁逻辑忽略
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// ...根据状态判断是否成功释放,省略
//写volatile变量,重要
setState(c); // line 2
return free;
}
假设有A,B两个线程执行count++,A线程抢到执行权,B线程阻塞。A线程释放锁时会走line 2,这时B线程继续争抢执行权执行line 1。
-
对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
A线程释放锁setState一定生效可见于B线程获取锁getState
-
一个线程中的每个操作,happens-before于该线程中的任意后续操作。
A线程的count++一定生效可见于A线程解锁
-
如果A happens-before B,且B happens-before C,那么A happens-before C。
A线程的count++一定生效可见于B线程获取锁getState以及后续的业务处理
synchronized的内存可见性
-
对一个锁的解锁,happens-before于随后对这个锁的加锁。
A线程的解锁一定生效可见于B线程获取锁,后续流程分析与lock一致。
网友评论