了解启动线程和涉及的可能的数据竞争

时间:2016-01-10 06:57:27

标签: java multithreading

我正在阅读B. Goetz Java Concurrency In Practice,现在我在关于同步器的部分。他将latches描述为一种同步器,并为CountDownLatch提供了典型的用例:

public class TestHarness{

    public long timeTask(int nThreads, final Runnable task){

        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate = new CountDownLatch(nThreads);

        for(i = 0; i < nThreads; i++) {
           Thread t = new Thread(){
              try{
                  startGate.await();
                  try{
                      task.run();
                  } finally {
                      endGate.countDown();
                  }
              } catch (InterruptedException ignored){ }
           };
           t.start();   // <--- Here
        }

        long start = System.nanoTime();
        startGate.countDown();
        endGate.await();
        long end = System.nanoTime();
        return end - start;
    }

}

在这个例子中,锁存器的使用情况非常清楚,但问题是

为什么程序可能会被视为正确的同步

Accoridng to JLS 17.4

  

一个实现可以自由地生成它喜欢的任何代码,只要全部   由此产生的程序执行产生的结果可以是   由记忆模型预测。

可能会发生一些重新排序。我们这里没有任何明确的同步块。例如,为什么编译器在循环之前产生startGate.countDown的代码是不可能的。

2 个答案:

答案 0 :(得分:2)

看起来原版并不能保证所有线程都在准备就绪之前就像@ St.Antario所提到的那样。它确保在代码long start = System.nanoTime();

之前不会启动任何线程

有些线程可以在初始化所有线程之前开始运行。我认为如果代码想要阻止所有线程启动直到它们都准备就绪,那么代码必须是:

public class TestHarness{

public long timeTask(int nThreads, final Runnable task){

    final CountDownLatch startGate = new CountDownLatch(nThreads);//Changed this from 1
    final CountDownLatch endGate = new CountDownLatch(nThreads);

    for(i = 0; i < nThreads; i++) {
       Thread t = new Thread(){
          try{
              startGate.countDown(); //Reduce the latch count by 1
              startGate.await(); //Once the last Thread is ready, this will continue
              try{
                  task.run();
              } finally {
                  endGate.countDown();
              }
          } catch (InterruptedException ignored){ }
       };
       t.start();   // <--- Here
    }

    long start = System.nanoTime();
    endGate.await();
    long end = System.nanoTime();
    return end - start;
  }

}

这是一个测试工具,显示原始代码中的init和start序列:

import java.util.concurrent.CountDownLatch;

public class TestHarness{


    public long timeTask(int nThreads, final Runnable task) throws InterruptedException{

        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate = new CountDownLatch(nThreads);

        for(int i = 0; i < nThreads; i++) {
           Thread t = new Thread( new Runnable(){

            @Override
            public void run() {
                  try{
                      System.out.println("Init");
                      startGate.await();

                      try{
                          task.run();
                      } finally {
                          endGate.countDown();
                      }
                  } catch (InterruptedException ignored){ }
               };

            });

           t.start();   // <--- Here
        }
        long start = System.nanoTime();
        startGate.countDown();
        System.out.println("Open Gate");

        endGate.await();
        long end = System.nanoTime();
        return end - start;
 }

    public static void main(String[] args) throws Exception {
        new TestHarness().timeTask(10, new Runnable() {

            @Override
            public void run() {
              System.out.println("Am Running");

            }
        });
    }
}

如果您运行代码,有时您会看到:

Init
Open Gate
Am Running
Init

这意味着在调用startGate.countDown()之前,某些线程可能没有准备好/创建。

答案 1 :(得分:0)

  

为什么不可能,例如,编译器生成一个代码,其中startGate.countDown位于循环之前。

有可能。只是countDown()才会完成,直到其他线程都调用了countDown()