Java并发编程5-Java存储模式(三)

2014-11-23 23:34:46 · 作者: · 浏览: 3
olean innerIsDone() { return isRan(getState()) && runner == null; } private boolean isRan(int state) { return (state & RAN) != 0; } protected boolean tryReleaseShared(int ignore) { runner = null; return true; } V innerGet() throws InterruptedException, ExecutionException { acquireSharedInterruptibly(0); return result; } void innerSet(V v) { for (;;) { int s = getState(); if (s == RAN) return; if (compareAndSetState(s, RAN)) { result = v; releaseShared(0); return; } } } void innerRun() { if (!compareAndSetState(READY, RUNNING)) return; runner = Thread.currentThread(); if (getState() == RUNNING) { // recheck after setting thread V result; try { result = callable.call(); } catch (Throwable ex) { return; } set(result); } else { releaseShared(0); // cancel } } } }


下面是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具有可见性,通过对程序的分析,我们可以得出:

1)innerSet中对result赋值的操作happens-before对releaseShared的调用(程序次序法则);
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中,同步,甚至是无竞争的同步,都存在惊人的性能开销,许多聪明