使用线程池

时间:2017-04-28 05:51:00

标签: java multithreading threadpool factorial

我实现了使用没有池的两个线程计算阶乘。我有两个因子类,它们被命名为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毫秒

感谢您的想法和建议。

3 个答案:

答案 0 :(得分:1)

线程必须独立运行才能快速运行。许多依赖项(如锁,代码的同步部分或某些系统调用)会导致等待访问某些资源的休眠线程。

在您的情况下,您应该最小化线程在锁内的时间。也许我错了,但似乎你为每个数字创建一个线程。所以对于1.000!,你会产生1.000个线程。所有这些都试图锁定area并且无法计算任何内容,因为一个线程已成为锁定,所有其他线程必须等到锁定再次解锁。因此,线程只能以串行方式运行,这与非线程示例一样快,加上锁定和解锁,线程管理等的额外时间。哦,由于cpu的上下文切换,它变得更糟。

您首次尝试在两个线程中拆分阶乘是更好的一个。每个线程都可以计算自己的结果,只有当它们完成时,线程才能相互通信。所以他们大部分时间都是独立的。

现在你必须概括这个解决方案。为了减少cpu的上下文切换,你只需要与你的cpu有核心一样多的线程(因为你的操作系统可能会少一点)。每个帖子都有一大堆数字并计算他们的产品。在此之后它锁定整体结果并将其自己的结果添加到它。

这可以改善您的问题的性能。

更新:您需要更多建议:

你说你有两个班Factorial1Factorial2。可能他们有他们的范围硬编码。您只需要一个将范围作为构造函数参数的类。这个类实现了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;
}