熟悉Java中的线程:为什么这个程序的运行时随着线程数量的增加而增加

时间:2014-03-20 13:12:23

标签: java multithreading performance random

场合

我正在尝试熟悉Java中的线程。出于这个原因,我修改了我在书中找到的程序列表。做什么很简单:

  1. 它创建一个boolean[] - 数组,其中包含100.000.000个元素。
  2. 使用true主题随falseNUMBER_OF_SERVERS随机填充数组元素。
  3. 最后,它使用NUMBER_OF_SERVERS个线程扫描该数组,并计算设置为true
  4. 的条目数

    有关详细信息,请参阅本文底部的以下代码。

    问题

    当我运行具有不同线程数的代码并测量运行时时,我得到一个非常奇怪的结果;或者至少是我不理解的行为:当我使用MORE线程时,BuildService-Thread消耗更多运行时。在一个线程中构建整个数组(基于随机true分布)大约需要10秒。接下来,当我使用四个线程时,我希望运行时间减少。但是,我花了大约17秒的时间消耗。

    我的ScanService按预期工作:时间消耗随着线程的增加而减少。

    详情请参阅下表:

    Thread-Runtime with random <code>true</code>-distribution

    但是,如果在我的代码中更改一行并用if ((int) ((Math.random() * 2d)) == 0)替换true - 语句(对于随机if (i % 2 == 0) - 分发)(因此,每隔一项将为真)我得到一个我期望的行为:

    Thread-Runtime with even <code>true</code>-distribution

    问题

    所以,我的问题是:

    1. 使用Math.random()函数时,为什么更多线程会导致LONGER运行时?
    2. 反之亦然,为什么在使用完全相同的函数只使用一个线程时运行时会减少?
    3. 什么&#34;一般规则&#34;归结为处理线程时,可以从这种行为中得出什么?
    4. 背景信息

      代码在Intel核心i3上运行。

      代码

      public class AsynchService
      {
          private static final int ARRAY_SIZE = 100000000; //100.000.000
          private static final int NUMBER_OF_SERVERS = 16;
          private static final int HOW_MANY = ARRAY_SIZE / NUMBER_OF_SERVERS;
      
          //build array asynch
          public static boolean[] buildArrayAsynch()
          {
              //build array with NUMBER_OF_SERVERS-Threads
              boolean[] array = new boolean[ARRAY_SIZE];
              Thread[] buildServerThread = new Thread[NUMBER_OF_SERVERS];
      
              long startTime = System.currentTimeMillis();
      
              for (int i = 0; i < NUMBER_OF_SERVERS; i++)
              {
                  int start = i * HOW_MANY;
                  int end = (i != NUMBER_OF_SERVERS - 1) ? (i + 1) * HOW_MANY - 1 : ARRAY_SIZE - 1;
      
                  buildServerThread[i] = new BuildService(array, i, start, end);
              }
      
              //synchronize and wait for result
              int expectedResult = 0;
      
              for (int i = 0; i < NUMBER_OF_SERVERS; i++)
              {
                  try
                  {
                      buildServerThread[i].join();
                  }
                  catch (InterruptedException ex) {}
      
                  expectedResult += ((BuildService) buildServerThread[i]).getExpectedResult();
              }
      
              System.out.println("\nNumber of \"true\"s ==> Expected result: " + expectedResult);
              System.out.println("Build duration: " + (System.currentTimeMillis() - startTime) + " ms\n");
      
              return array;
          }
      
          //scan array asynch
          public static int scanArrayAsynch(boolean[] array)
          {
              //create services and server-threads
              Thread[] serverThread = new Thread[NUMBER_OF_SERVERS];
      
              long startTime = System.currentTimeMillis();
      
              for (int i = 0; i < NUMBER_OF_SERVERS; i++)
              {
                  int start = i * HOW_MANY;
                  int end = (i != NUMBER_OF_SERVERS - 1) ? (i + 1) * HOW_MANY - 1 : ARRAY_SIZE - 1;
      
                  serverThread[i] = new ScanService(array, i, start, end);
              }
      
              //synchronize with servers, wait for server end
              int result = 0;
      
              for (int i = 0; i < NUMBER_OF_SERVERS; i++)
              {
                  try
                  {
                      serverThread[i].join();
                  }
                  catch (InterruptedException ex) {}
      
                  result += ((ScanService) serverThread[i]).getResult();
              }
      
              System.out.println("Search duration: " + (System.currentTimeMillis() - startTime) + " ms");
              return result;
          }
      
          public static void main(String[] args)
          {
              //build array
              boolean[] array = buildArrayAsynch();
      
              //scan array
              int result = scanArrayAsynch(array);
      
              //display result
              System.out.println("\nResult: " + result);
      
          }
      }
      
      class BuildService extends Thread
      {
          private boolean[] array;
          private int start;
          private int end;
          private int expectedResult = 0;
      
          public BuildService(boolean[] array, int serviceId, int start, int end)
          {
              this.array = array;
              this.start = start;
              this.end = end;
      
              this.setName("BuildService " + serviceId);
      
              this.start();
          }
      
          public int getExpectedResult()
          {
              return expectedResult;
          }
      
          public void run()
          {
              if (start < 0 || end >= array.length) throw new IndexOutOfBoundsException();
      
              System.out.println(getName() + ": StartIndex = " + start + "; EndIndex = " + end);
      
              long startTime = System.currentTimeMillis();
      
              for (int i = start; i <= end; i++)
              {
                  //if (i % 2 == 0)
                  if ((int) ((Math.random() * 2d)) == 0)
                  {
                      array[i] = true;
                      expectedResult++;
                  }
                  else
                  {
                      array[i] = false;
                  }
              }
      
              System.out.println(getName() + " finished! \"true\" elements: " + expectedResult + "; duration = " + (System.currentTimeMillis() - startTime) + "ms");
          }
      }
      
      class ScanService extends Thread
      {
          private boolean[] array;
          private int serviceId;
          private int start;
          private int end;
          private int result = 0;
      
          public ScanService(boolean[] array, int serviceId, int start, int end)
          {
              this.array = array;
              this.serviceId = serviceId;
              this.start = start;
              this.end = end;
      
              this.start();
          }
      
          public int getResult()
          {
              return result;
          }
      
          public void run()
          {
              if (start < 0 || end >= array.length) throw new IndexOutOfBoundsException();
      
              System.out.println("Server " + serviceId + ": StartIndex = " + start + "; EndIndex = " + end);
      
              for (int i = start; i <= end; i++)
              {
                  if (array[i]) result++;
              }
          }
      }
      

4 个答案:

答案 0 :(得分:3)

魔鬼在于细节。 Math.random()的{​​{3}}有答案:

  

此方法已正确同步,以允许多个线程正确使用。但是,如果许多线程需要以很高的速率生成伪随机数,则可以减少每个线程争用自己的伪随机数生成器的争用。

要解决此问题,请尝试为BuildService类的每个实例创建一个java.util.Random实例,并使用该实例代替Math.random()。使用java.util.Random的好处还在于您不必执行不必要的双重算术,但可以使用nextBoolean()方法。

答案 1 :(得分:1)

您可能会在Math.random()中看到线程争用的影响。 Java 7具有ThreadLocalRandom功能以避免此问题。

答案 2 :(得分:0)

您的代码似乎使用了16个线程,并且每个线程都使用了Join方法,如此处

serverThread [I]。加入();

似乎没有充分发挥线程的潜力。

当使用连接时,你实际上是在说线程要等到另一个线程完成没有并行运行线程。

您可能希望使用start方法而不是join方法。

尝试运行更改的代码并在时间线上发布分析。

祝你好运学习

答案 3 :(得分:0)

考虑到Andreas Troelsen的回答,我想出了下面显示的代码,导致以下运行时。

runtimes utilizing objects of java.util.Random

与之前发生的情况相比,此解决方案现在更符合我的期望!

import java.util.Random;

class BuildService extends Thread
{
    private boolean[] array;
    private int start;
    private int end;
    private int expectedResult = 0;
    private Random random = new Random();

    public BuildService(boolean[] array, int serviceId, int start, int end)
    {
        this.array = array;
        this.start = start;
        this.end = end;

        this.setName("BuildService " + serviceId);

        this.start();
    }

    public int getExpectedResult()
    {
        return expectedResult;
    }

    public void run()
    {
        if (start < 0 || end >= array.length) throw new IndexOutOfBoundsException();

        System.out.println(getName() + ": StartIndex = " + start + "; EndIndex = " + end);

        long startTime = System.currentTimeMillis();

        for (int i = start; i <= end; i++)
        {
            array[i] = random.nextBoolean();
            if (array[i]) expectedResult++;
        }

        System.out.println(getName() + " finished! \"true\" elements: " + expectedResult + "; duration = " + (System.currentTimeMillis() - startTime) + "ms");
    }
}