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

2014-11-23 23:34:46 · 作者: · 浏览: 1
所有的动作B都出现在动作A之后;
监视器锁法则:对一个监视器锁的解锁Happens-before于每一个后续对同一监视器锁的加锁;
volatile变量法则:对volatile域的写入操作Happens-before于每一个后续对同一域的读操作;
线程启动法则:在一个线程中,对Thread.start的调用会Happens-before于每一个启动线程中的动作;
线程终结法则:线程中的任何动作都Happens-before于其他线程检测到这个线程已经终结(或者从Thread.join调用中成功返回,或者Thread.isAlive返回false);
中断法则:一个线程调用另一个线程的interrupt Happens-before于被中断的线程发现中断(通过抛出InterruptedException,或者调用isInterrupted和interrupted);
终结法则:一个对象的构造函数的结束Happens-before于这个对象finalizer的开始;
传递性法则:如果A Happens-before于B,且B Happens-before于C,则A Happens-before于C。

J:怎么感觉程序次序法则和重排序有点矛盾呢?
T:程序次序法则并不是暗示两个动作必定按照程序的顺序发生,如果重排序的结果和合法执行的结果是一致的,那么重排序后也是合法的。这样说太过于抽象了,举个例子吧:

public class Test {
	private static int x = 0;
	private static int y = 0;

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				x = 5;
				y = 6;
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				if (y == 6) {
					System.out.println(x);
				}
			}
		});
		t1.start();
		t2.start();
		t1.join();
		t2.join();
	}
}


根据重排序,t2线程打印的结果可能为5,也可能为0。根据程序次序法则,x=5 happens-before y=6,但是t2的执行和t1的执行之间不存在happens-before关系,因此t2中在y==6的情况下就可能看到x==5,也可能看到x==0。但是,如果y是volatile变量,我们可以看到:
1)根据程序次序法则:x=5 happens-before y=6;
2)根据volatile变量法则:y=6 happens-before 读取y;
3)根据程序次序法则:读取y happens-before 读取x。
最后,根据传递性法则,我们就可以保证t2的打印结果始终为5。
J:通过这个例子来看就好多了,我想我已经差不多明白happens-before了。
T:我下面将分别用一个正面的例子和一个反面的例子来说明Happens-before的运用,我们从正面的例子开始:

FutureTask

FutureTask位于java.util.concurrent包下面,它实现了Runnable和Future接口。打算通过线程执行其实例的类都需要实现Runnable接口,而Future接口则用于表示异步计算的接口,主要包含方法:
cancel:试图取消对此任务的执行。
get:如有必要,等待计算完成,然后获取其结果,可以指定时间。
isCancelled:如果在任务正常完成前将其取消,则返回 true。
isDone:如果任务已完成,则返回 true。

一个通常的使用FutureTask的例子如下:

public class Test {
	public static void main(String[] args) throws InterruptedException,
			ExecutionException {
		ExecutorService executor = Executors.newFixedThreadPool(10);
		try {
			Future
  
    future = executor.submit(new Callable
   
    () { @Override public String call() throws Exception { Thread.sleep(2000); return "It is done!"; } }); System.out.println(future.get()); } finally { executor.shutdown(); } } }
   
  


我们这里的讲解聚焦于FutureTask对happens-before法则的使用,为了帮组我们对FutureTask的源码的分析,下面我实现了一个简化版的FutureTask(去掉了cancel功能和对异常的处理部分,代码存在缺陷,完整的代码请看JDK源码:java.util.concurrent.FutureTask.java):

public class FutureTask
  
    implements Runnable {
	private final Sync sync;

	public FutureTask(Callable
   
     callable) { sync = new Sync(callable); } @Override public void run() { sync.innerRun(); } public V get() throws InterruptedException, ExecutionException { return sync.innerGet(); } protected void set(V v) { sync.innerSet(v); } private final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -7828117401763700385L; /** State value representing that task is ready to run */ private static final int READY = 0; /** State value representing that task is running */ private static final int RUNNING = 1; /** State value representing that task ran */ private static final int RAN = 2; private final Callable
    
      callable; private V result; private volatile Thread runner; Sync(Callable
     
       callable) { this.callable = callable; } protected int tryAcquireShared(int ignore) { return innerIsDone()   1 : -1; } bo