我实现了使用没有池的两个线程计算阶乘。我有两个因子类,它们被命名为Factorial1,Factorial2并扩展了Thread类。让我们考虑一下,我想计算!160000的值。在Factorial1的run()方法中,我在for循环中进行乘法,从i = 2到i = 80000,在Factorial2中从i = 80001到160000进行乘法。之后,我返回两个值并将它们相乘在主要方法。当我比较执行时间时,它比非线程计算的时间(15000毫秒)好得多(即5000毫秒),即使有两个线程。
现在我想编写干净且更好的代码,因为我在阶乘计算中看到了线程的效率,但是当我使用线程池来计算阶乘值时,并行计算总是花费比非线程计算更多的时间(几乎16000)。我的代码片段如下:
for(int i=2; i<= Calculate; i++)
{
myPool.execute(new Multiplication(result, i));
}
run()方法,它位于乘法类中:
public void run()
{
s1.Mltply(s2); // s1 and s2 are instances of my Number class
// their fields holds BigInteger values
}
Mltply()方法,在Number类中:
public void Multiply(int number)
{
area.lock(); // result is going wrong without lock
Number temp = new Number(number);
value = value.multiply(temp.value); // value is a BigInteger
area.unlock();
}
在我看来,这个锁可能会杀死线程使用的所有优点,因为看起来所有线程都是乘法而不是别的。但没有它,我甚至无法计算出真实的结果。让我们说我想计算!10,所以thread1计算10 * 9 * 8 * 7 * 6,thread2计算5 * 4 * 3 * 2 * 1。这是我正在寻找的方式吗?是否可以使用线程池?当然执行时间必须小于正常计算...
感谢您的所有帮助和建议。
编辑: - 我自己解决问题的方法 -
public class MyMultiplication implements Runnable
{
public static BigInteger subResult1;
public static BigInteger subResult2;
int thread1StopsAt;
int thread2StopsAt;
long threadId;
static boolean idIsSet=false;
public MyMultiplication(BigInteger n1, int n2) // First Thread
{
MyMultiplication.subResult1 = n1;
this.thread1StopsAt = n2/2;
thread2StopsAt = n2;
}
public MyMultiplication(int n2,BigInteger n1) // Second Thread
{
MyMultiplication.subResult2 = n1;
this.thread2StopsAt = n2;
thread1StopsAt = n2/2;
}
@Override
public void run()
{
if(idIsSet==false)
{
threadId = Thread.currentThread().getId();
idIsSet=true;
}
if(Thread.currentThread().getId() == threadId)
{
for(int i=2; i<=thread1StopsAt; i++)
{
subResult1 = subResult1.multiply(BigInteger.valueOf(i));
}
}
else
{
for(int i=thread1StopsAt+1; i<= thread2StopsAt; i++)
{
subResult2 = subResult2.multiply(BigInteger.valueOf(i));
}
}
}
}
public class JavaApplication3
{
public static void main(String[] args) throws InterruptedException
{
int calculate=160000;
long start = System.nanoTime();
BigInteger num = BigInteger.valueOf(1);
for (int i = 2; i <= calculate; i++)
{
num = num.multiply(BigInteger.valueOf(i));
}
long end = System.nanoTime();
double time = (end-start)/1000000.0;
System.out.println("Without threads: \t" +
String.format("%.2f",time) + " miliseconds");
System.out.println("without threads Result: " + num);
BigInteger num1 = BigInteger.valueOf(1);
BigInteger num2 = BigInteger.valueOf(1);
ExecutorService myPool = Executors.newFixedThreadPool(2);
start = System.nanoTime();
myPool.execute(new MyMultiplication(num1,calculate));
Thread.sleep(100);
myPool.execute(new MyMultiplication(calculate,num2));
myPool.shutdown();
while(!myPool.isTerminated()) {} // waiting threads to end
end = System.nanoTime();
time = (end-start)/1000000.0;
System.out.println("With threads: \t" +String.format("%.2f",time)
+ " miliseconds");
BigInteger result =
MyMultiplication.subResult1.
multiply(MyMultiplication.subResult2);
System.out.println("With threads Result: " + result);
System.out.println(MyMultiplication.subResult1);
System.out.println(MyMultiplication.subResult2);
}
}
输入:!160000
没有线程的执行时间:15000毫秒
2个线程的执行时间:4500毫秒
感谢您的想法和建议。
答案 0 :(得分:1)
线程必须独立运行才能快速运行。许多依赖项(如锁,代码的同步部分或某些系统调用)会导致等待访问某些资源的休眠线程。
在您的情况下,您应该最小化线程在锁内的时间。也许我错了,但似乎你为每个数字创建一个线程。所以对于1.000!
,你会产生1.000个线程。所有这些都试图锁定area
并且无法计算任何内容,因为一个线程已成为锁定,所有其他线程必须等到锁定再次解锁。因此,线程只能以串行方式运行,这与非线程示例一样快,加上锁定和解锁,线程管理等的额外时间。哦,由于cpu的上下文切换,它变得更糟。
您首次尝试在两个线程中拆分阶乘是更好的一个。每个线程都可以计算自己的结果,只有当它们完成时,线程才能相互通信。所以他们大部分时间都是独立的。
现在你必须概括这个解决方案。为了减少cpu的上下文切换,你只需要与你的cpu有核心一样多的线程(因为你的操作系统可能会少一点)。每个帖子都有一大堆数字并计算他们的产品。在此之后它锁定整体结果并将其自己的结果添加到它。
这可以改善您的问题的性能。
更新:您需要更多建议:
你说你有两个班Factorial1
和Factorial2
。可能他们有他们的范围硬编码。您只需要一个将范围作为构造函数参数的类。这个类实现了Runnable,因此它有一个run
- Method,它将该范围内的所有值相乘。
在你main
- 方法中你可以这样做:
int n = 160_000;
int threads = 2;
ExecutorService executor = Executors.newFixedThreadPool(threads);
for (int i = 0; i < threads; i++) {
int start = i * (n/threads) + 1;
int end = (i + 1) * (n/threads) + 1;
executor.execute(new Factorial(start, end));
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.DAYS);
现在您已经计算了每个线程的结果,但没有计算出整体结果。这可以通过BigInteger
类(Factorial
类可见)来解决(如同一主类中的static BigInteger reuslt;
。)和锁定。在run
- Factorial
方法中,您可以通过锁定锁定并计算结果来计算总体结果:
Main.lock.lock();
Main.result = Main.result.multiply(value);
Main.lock.unlock();
对未来的一些额外建议:这并不是非常简洁,因为Factorial
需要有关于主类的信息,因此它依赖于它。但ExecutorService
返回一个Future<T>
- 对象,可用于接收线程的结果。使用此Future
- 对象,您不需要使用锁。但这需要一些额外的工作,所以现在试着让它运行起来; - )
答案 1 :(得分:0)
您可以通过将!160000
拆分为分离的junks而不使用锁来同时计算160000
,因为您通过将其拆分为2..80000和80001..160000进行解释。
但您可以通过使用Java Stream API实现此目的:
IntStream.rangeClosed(1, 160000).parallel()
.mapToObj(val -> BigInteger.valueOf(val))
.reduce(BigInteger.ONE, BigInteger::multiply);
它完全符合您的要求。它将整个范围拆分为junks,建立一个线程池并计算部分结果。之后,它将部分结果加入到单个结果中。
那你为什么要自己去做呢?只是练习清洁编码?
在我的实际4核心机器中,在for循环中的计算时间比使用并行流的时间长8倍。
答案 2 :(得分:0)
除了我的 Java Stream API 解决方案之外,还有另一种解决方案,它根据您的要求使用自我管理的线程池:
public static final int CHUNK_SIZE = 10000;
public static BigInteger fac(int max) {
ExecutorService executor = newCachedThreadPool();
try {
return rangeClosed(0, (max - 1) / CHUNK_SIZE)
.mapToObj(val -> executor.submit(() -> prod(leftBound(val), rightBound(val, max))))
.map(future -> valueOf(future))
.reduce(BigInteger.ONE, BigInteger::multiply);
} finally {
executor.shutdown();
}
}
private static int leftBound(int chunkNo) {
return chunkNo * CHUNK_SIZE + 1;
}
private static int rightBound(int chunkNo, int max) {
return Math.min((chunkNo + 1) * CHUNK_SIZE, max);
}
private static BigInteger valueOf(Future<BigInteger> future) {
try {
return future.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static BigInteger prod(int min, int max) {
BigInteger res = BigInteger.valueOf(min);
for (int val = min + 1; val <= max; val++) {
res = res.multiply(BigInteger.valueOf(val));
}
return res;
}