场合
我正在尝试熟悉Java中的线程。出于这个原因,我修改了我在书中找到的程序列表。做什么很简单:
boolean[]
- 数组,其中包含100.000.000个元素。true
主题随false
或NUMBER_OF_SERVERS
随机填充数组元素。NUMBER_OF_SERVERS
个线程扫描该数组,并计算设置为true
有关详细信息,请参阅本文底部的以下代码。
问题
当我运行具有不同线程数的代码并测量运行时时,我得到一个非常奇怪的结果;或者至少是我不理解的行为:当我使用MORE线程时,BuildService-Thread消耗更多运行时。在一个线程中构建整个数组(基于随机true
分布)大约需要10秒。接下来,当我使用四个线程时,我希望运行时间减少。但是,我花了大约17秒的时间消耗。
我的ScanService按预期工作:时间消耗随着线程的增加而减少。
详情请参阅下表:
但是,如果在我的代码中更改一行并用if ((int) ((Math.random() * 2d)) == 0)
替换true
- 语句(对于随机if (i % 2 == 0)
- 分发)(因此,每隔一项将为真)我得到一个我期望的行为:
问题
所以,我的问题是:
背景信息
代码在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++;
}
}
}
答案 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的回答,我想出了下面显示的代码,导致以下运行时。
与之前发生的情况相比,此解决方案现在更符合我的期望!
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");
}
}