使用join()的Java多线程程序在计算相邻数字之和时给出错误结果

use*_*321 5 java concurrency multithreading java-memory-model java-threads

我编写了这个简单的多线程程序,将1到100,000之间的数字相加。运行此命令时,最终结果得到的值不同(值小于预期的5000050000)。当我仅使用一个线程执行程序时,它给出了正确的结果。该程序还适用于较小的值,例如100。可能出了什么问题?提前致谢。

class Calculation {

  private long total=0;
  public void calcSum(long start, long end) {
      long i = start;
      for( ;i<=end; i++) {
          total += i;
      }
  }
  public long getTotal() {
     return total;
  }
}

class CalculatorThread extends Thread{
   private long start;
   private long end;
   private Calculation calc;
   public CalculatorThread(long start, long end, Calculation calc) {
       this.start = start;
       this.end = end;
       this.calc = calc;
   }
   @Override
   public void run() {
       calc.calcSum(start, end);
   }
}

public class ParallelTest {

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

      int start = 1;
      int end = 100000;

      Calculation calc = new Calculation();
      CalculatorThread ct1 = new CalculatorThread(start, end/2 , calc);
      CalculatorThread ct2 = new CalculatorThread( (end/2) + 1, end, calc);

      ct1.start();
      ct2.start();

      ct1.join();
      ct2.join();

      System.out.println(calc.getTotal());
  }
}
Run Code Online (Sandbox Code Playgroud)

And*_*kin 3

对共享可变状态的非同步访问通常不会顺利进行。

在您的中Calculation calc,有一个可变变量long total。当您启动线程时:

  CalculatorThread ct1 = new CalculatorThread(start, end/2 , calc);
  CalculatorThread ct2 = new CalculatorThread( (end/2) + 1, end, calc);
Run Code Online (Sandbox Code Playgroud)

calc您共享这两个线程之间的可变状态。内部没有任何同步calc,因此线程只会以随机的时间间隔垃圾彼此的内存。

这是一个工作版本:

class ParallelSum {
  public static long calcSum(long start, long end) {
      long total = 0;
      for(long i = start; i < end; i++) {
          total += i;
      }
      return total;
  }

  public static class CalculatorThread extends Thread {
     private long result = 0;
     private long start;
     private long end;
     public CalculatorThread(long start, long end) {
         this.start = start;
         this.end = end;
     }
     @Override
     public void run() {
         result = calcSum(start, end);
     }
     public long getResult() {
         return result;
     }
  }

  public static void main(String[] args) throws InterruptedException {
    int start = 1;
    int end = 100000;
    int endExcl = end + 1;

    CalculatorThread ct1 = new CalculatorThread(start, endExcl/2);
    CalculatorThread ct2 = new CalculatorThread(endExcl / 2, endExcl);

    ct1.start();
    ct2.start();

    ct1.join();
    ct2.join();

    System.out.println(ct1.getResult() + ct2.getResult());
  }
}
Run Code Online (Sandbox Code Playgroud)

输出:

5000050000
Run Code Online (Sandbox Code Playgroud)

附加说明:始终使用[inclusive, exclusive)范围索引。这极大地降低了出现相差一错误的机会。另外,我用方法替换了Calculation类:方法内部的局部变量不会出错,并且可变状态越少越好。