下面是AQS,这里只列举相关的代码部分,我会在后面的文章中详细介绍AQS,这里只对我们关心的流程做简单介绍。
public abstract class AbstractQueuedSynchronizer extends
AbstractOwnableSynchronizer implements java.io.Serializable {
.......
//判断tryAcquireShared是否满足,不满足进入doAcquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//线程将在这里等待,直到条件满足后返回
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//每次唤醒后调用tryAcquireShared,看条件是否满足
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//parkAndCheckInterrupt中线程进入等待
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//tryReleaseShared中将runner设置为null,然后在doReleaseShared唤醒等待的线程
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒等待线程
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
......
}
认真看一下上面的代码,然后回答下面两个问题:
1)runner变量为什么要声明为volatile变量;
2)result为什么不需要声明为volatile变量。
J:这个有点难,得花点时间。
...一个小时之后...
J:我大概明白为什么runner需要声明为volatile变量,但还是不明白为什么result不需要声明为volatile变量。
T:好吧,那我们先从runner开始吧:runner会在tryReleaseShared方法中置空,tryReleaseShared在执行run的线程中调用;runner会在innerIsDone方法中判断是否为空,innerIsDone是在调用get方法的线程中调用。因此,runner会在一个线程中修改,又在另一个线程中读取,为了保证可见性,runner需要被声明为volatile变量。
那result为什么不需要声明为volatile变量呢?它是靠什么来保证它的可见性的呢?下面我将使用happens-before规则来证明result具有可见性,通过对程序的分析,我们可以得出:
2)releaseShared中调用releaseShared;
3)releaseShared的调用成功happens-before tryAcquireShared的成功调用(volatile变量法则);
4)acquireSharedInterruptibly中调用tryAcquireShared;
5)innerGet中acquireSharedInterruptibly的调用成功happens-before result结果的返回(程序次序法则)。
从上面的分析,然后根据传递性法则:innerSet对result赋值的操作happens-before innerGet中result结果的返回。 由此,证明完成,结果成立,也就是说get的线程始终能够获取到正确的result值。
J(开心):我现在已经彻底的理解happens-before了。
T:恭喜恭喜。FutureTask的例子为我们展示了怎样利用happens-before原则“驾奴”在同步之上,这样做的好处在于性能的提升,坏处是非常容易出错,因此使用时一定要非常小心。
J:好的,那我们继续开始反例吧。
T:好的。
双检查锁(Double-Checked Locking,DCL)
这是一个经典的例子,在早期版本的JVM中,同步,甚至是无竞争的同步,都存在惊人的性能开销,许多聪明