Skip to content

Files

Latest commit

166cb6c · Jun 10, 2017

History

History
681 lines (441 loc) · 19.9 KB

Happens-before.md

File metadata and controls

681 lines (441 loc) · 19.9 KB

happens-before

happens-before是保证Memory Visibility的前提,根据javadoc的说明:

The results of a write by one thread are guaranteed to be 可见 to a read by another thread only if the write operation happens-before the read operation。

这句话的意思说,只有当happens-before的时候,一个write operation才对另一个read operation可见。换句话说,就是read operation能够读到write operation的结果。

基本的happens-before关系

下面讲解由synchronizedvolatileThread.start()Thread.join()形成的happens-before关系。

无happens-before的例子

不利用任何java.util.concurrent工具、synchronizedvolatile关键字时,是无法保证happens-before机制的。

NoHappensBefore.java的代码:

public class NoHappensBefore {

  private static boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> {

      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");
    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);
    stop = true;

  }

}

在上面这个例子里,t1有可能永远都不结束,因为main threadstop变量修改对于t1 不可见。

同thread中的每个action happens-before 后一个action

根据javadoc的说明:

Each action in a thread happens-before every action in that thread that comes later in the program's order.

InThreadHappensBefore.java的代码:

public class InThreadHappensBefore {

  private static boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> {
      stop = true;
      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

  }
}

这里例子里,stop = truewhile (!stop)是在一同一个thread中的,所以前者对后者visible。

synchronized 保证 happens-before

根据javadoc的说明:

An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.

这里提供了两个例子:

synchronized修饰的方法保证happens-before的例子:SynchronizedMethodHappensBefore.java

public class SynchronizedMethodHappensBefore {

  private static boolean stop = false;

  private static synchronized boolean isStop() {
    return stop;
  }

  private static synchronized void markStop() {
    stop = true;
  }

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> {

      while (!isStop()) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);
    markStop();

  }

}

synchronized block保证happens-before的例子:SynchronizedBlockHappensBefore

public class SynchronizedBlockHappensBefore {

  private static boolean stop = false;

  private static Object monitor = new Object();

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> {
      while (!stop) {
        synchronized (monitor) {
          // 空的,只是为了获得monitor锁
        }
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);
    synchronized (monitor) {
      stop = true;
    }
  }

}

volatile 保证 happens-before

根据javadoc的说明:

A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.

VolatileHappensBefore.java的代码:

public class VolatileHappensBefore {

  private static volatile boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> {

      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);
    stop = true;

  }

}

stop变量用了volatile修饰,因此对stop变量的修改对于t1 可见。

Thread#start happens-before

根据javadoc的说明:

A call to start on a thread happens-before any action in the started thread.

ThreadStartHappensBefore.java的代码:

public class ThreadStartHappensBefore {

  private static boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    stop = true;

    Thread t1 = new Thread(() -> {

      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

  }
}

happens-before具有传递性,stop = true happens-before t1.start()t1.start() happens-before t1中的所有操作,所以stop = true happens-before t1里的所有动作。

Thread#join happens-before

根据javadoc的说明:

All actions in a thread happen-before any other thread successfully returns from a join on that thread.

ThreadJoinHappensBefore.java的代码:

public class ThreadJoinHappensBefore {

  private static boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    Thread mainThread = Thread.currentThread();

    Thread t1 = new Thread(() -> {

      try {
        mainThread.join();
      } catch (InterruptedException e) {
        return;
      }

      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);
    stop = true;
    System.out.println(Thread.currentThread().getName() + " finished");

  }
  
}

注意上面的例子:t1mainThread.join(),当join方法返回后,main thread的所有操作都对t1 可见。

扩展的happens-before关系

根据javadoc的说明:

The methods of all classes in java.util.concurrent and its subpackages extend these guarantees to higher-level synchronization.

下面对每一个进行讲解。

Concurrent collection 保证 happens-before

根据javadoc的说明:

Actions in a thread prior to placing an object into any concurrent collection happen-before actions subsequent to the access or removal of that element from the collection in another thread.

ConcurrentCollectionHappensBefore.java的代码:

public class ConcurrentCollectionHappensBefore {

  private static boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    // commonCollectionNeverEnds();
    concurrentCollectionWillEnds();
  }

  private static void concurrentCollectionWillEnds() throws InterruptedException {

    ConcurrentLinkedQueue<Integer> conQueue = new ConcurrentLinkedQueue<>();

    Thread t1 = new Thread(() -> {

      while (!stop) {
        conQueue.peek();
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);
    stop = true;
    conQueue.add(1);

  }

  private static void commonCollectionNeverEnds() throws InterruptedException {

    Queue<Integer> conQueue = new LinkedList<>();

    Thread t1 = new Thread(() -> {

      while (!stop) {
        conQueue.peek();
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);
    stop = true;
    conQueue.add(1);

  }

}

在上面这个例子中concurrentCollectionWillEnds方法利用Concurrent Collection的happens-before特性,让stop变量的变化对t1 可见。 而普通的Collection不具有happens-before特性,所以stop变量的变化对于t1依然是invisible。

Executor happens-before

根据javadoc的说明:

Actions in a thread prior to the submission of a Runnable to an Executor happen-before its execution begins. Similarly for Callables submitted to an ExecutorService.

ExecutorSubmissionHappensBefore.java的代码:

public class ExecutorSubmissionHappensBefore {

  private static boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    ExecutorService executor = Executors.newSingleThreadExecutor();

    stop = true;

    executor.submit(() -> {

      Thread.currentThread().setName("t1");
      
      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    });

    executor.shutdown();

  }

}

这个和ThreadStartHappensBefore.java有点类似。

Future happens-before

根据javadoc的说明:

Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread.

FutureHappensBefore.java的代码:

public class FutureHappensBefore {

  private static boolean stop = false;

  public static void main(String[] args) throws InterruptedException {

    ExecutorService executor = Executors.newFixedThreadPool(2);

    Future<?> mainThreadFuture = executor.submit(() -> {
      
      // try ...
      TimeUnit.SECONDS.sleep(5L);
      // catch ...
      
      stop = true;
      System.out.println("main thread future finished");

    });

    executor.submit(() -> {

      Thread.currentThread().setName("t1");

      // try ...
      mainThreadFuture.get();
      // catch ...
      
      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    });

    executor.shutdown();

  }

}

在这个例子里t1mainThreadFuture.get(),那么mainThreadFuture对于stop的修改在Future.get()返回的时候,对t1可见。

synchronizer对象的释放happens-before获取

根据javadoc的说明:

Actions prior to "releasing" synchronizer methods such as Lock.unlock, Semaphore.release, and CountDownLatch.countDown happen-before actions subsequent to a successful "acquiring" method such as Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await on the same synchronizer object in another thread.

也就是说:

  1. Lock.unlock之前的动作,对于Lock.lock之后的动作可见
  2. Semaphore.release之前的动作,对于Semaphore.acquire之后的动作可见
  3. CountDownLatch.countDown之前的动作,对于CountDownLatch.await之后的动作可见

LockReleaseHappensBefore.java的代码:

public class LockReleaseHappensBefore {

  private static boolean stop = false;

  private static Lock lock = new ReentrantLock();

  public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> {
      while (!stop) {
        lock.lock();
        // 什么都不做,只是为了获得锁
        lock.unlock();
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.MILLISECONDS.sleep(5000L);

    lock.lock();
    stop = true;
    lock.unlock();
  }

}

这个例子和SynchronizedBlockHappensBefore.java有点像。

Exchanger happens-before

根据javadoc的说明:

For each pair of threads that successfully exchange objects via an Exchanger, actions prior to the exchange() in each thread happen-before those subsequent to the corresponding exchange() in another thread.

ExchangerHappensBefore.java的代码:

注意t1First里面,t1先进入Exchanger.exchangemain thread后进入Exchange.exchange

  private static void t1First() throws InterruptedException {
    System.out.println("============ t1First ============");

    Exchanger exchanger = new Exchanger();

    Thread t1 = new Thread(() -> {

      String threadName = Thread.currentThread().getName();
      try {
        System.out.println(threadName + " await exchanging");
        System.out.println(threadName + " exchanged: " + exchanger.exchange("from t1"));
      } catch (InterruptedException e) {
        return;
      }

      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.SECONDS.sleep(2L);
    stop = true;
    String threadName = Thread.currentThread().getName();
    System.out.println(threadName + " await exchanging");
    System.out.println(threadName + " exchanged: " + exchanger.exchange("from main thread"));

  }

mainThreadFirst里,main thread先进入Exchange.exchanget1后进入Exchanger.exchange

  private static void mainThreadFirst() throws InterruptedException {
    System.out.println("============ mainThreadFirst ============");

    Exchanger exchanger = new Exchanger();

    Thread t1 = new Thread(() -> {

      String threadName = Thread.currentThread().getName();
      try {
        TimeUnit.SECONDS.sleep(2L);
        System.out.println(threadName + " await exchanging");
        System.out.println(threadName + " exchanged: " + exchanger.exchange("from t1"));
      } catch (InterruptedException e) {
        return;
      }

      while (!stop) {
      }
      System.out.println(Thread.currentThread().getName() + " finished");
    }, "t1");

    t1.start();

    TimeUnit.SECONDS.sleep(1L);
    stop = true;
    String threadName = Thread.currentThread().getName();
    System.out.println(threadName + " await exchanging");
    System.out.println(threadName + " exchanged: " + exchanger.exchange("from main thread"));

  }

从上面可以看到,谁先谁后都不影响stopt1可见,所以Exchanger.exchange()happens-before具有对称性。

也就是说对于同一个Exchanger所涉及到的两个线程,其中任意一个线程中在exchange()之前的操作,都对另一个线程中在exchange()之后的操作可见。

CyclicBarrier happens-before

根据javadoc的说明:

Actions prior to calling CyclicBarrier.await and Phaser.awaitAdvance (as well as its variants) happen-before actions performed by the barrier action, and actions performed by the barrier action happen-before actions subsequent to a successful return from the corresponding await in other threads.

也就是说:

  1. CyclicBarrier.await之前的action happens-before barrier action
  2. barrier action happens-before CyclicBarrier.await之后的action

CyclicBarrierHappensBefore.java的代码:

先看一段第1点对应的代码:

private static void actionBeforeAwaitHappensBeforeBarrierAction() throws BrokenBarrierException, InterruptedException {

  CyclicBarrier barrier = new CyclicBarrier(1, () -> {
    while (!stop) {
    }
    System.out.println(Thread.currentThread().getName() + " finished");
  });

  Thread t1 = new Thread(() -> {

    try {
      TimeUnit.SECONDS.sleep(2L);
      stop = true;
      System.out.println(Thread.currentThread().getName() + " await barrier");
      barrier.await();
    } catch (InterruptedException e) {
      return;
    } catch (BrokenBarrierException e) {
      return;
    }

  }, "t1");

  t1.start();

}

从上面的代码可以看到,在t1 thread里我们在它await之前stop = true,然后在barrier action里读取stop变量。此时stop变量对于barrier action是可见的。

和第2点对应的代码:

private static void barrierActionHappensBeforeActionAfterAwait() throws InterruptedException, BrokenBarrierException {

  CyclicBarrier barrier = new CyclicBarrier(1, () -> stop = true);

  Thread t1 = new Thread(() -> {

    String threadName = Thread.currentThread().getName();

    try {
      System.out.println(threadName + " await barrier");
      while (!stop) {
        barrier.await();
      }

    } catch (InterruptedException e) {
      return;
    } catch (BrokenBarrierException e) {
      return;
    }
    System.out.println(Thread.currentThread().getName() + " finished");
  }, "t1");

  t1.start();

}

在上面的代码里,barrier actionstop = true,然后在t1 threadawait,此时stop变量对于t1 thread是可见的。

陷阱

happens-before无法解决Thread interference

需要注意的是,happens-before不能解决Thread interference的问题。

HappensBeforeThreadInterference.java的代码,其内部使用了VolatileCounter

public class VolatileCounter implements Counter {

  private volatile int count = 0;

  @Override
  public void increment() {
    count++;
  }

  @Override
  public void decrement() {
    count--;
  }

  @Override
  public int value() {
    return count;
  }

}

VolatileCounter#counter使用了volatile修饰,虽然这能解决memory visibility问题,但是依然解决不了Thread interference问题,运行的结果很大概率情况下不是0。

相关链接