代码性能的比较,线程与非线程

时间:2008-10-29 21:03:23

标签: java multithreading concurrency

我有一些线程相关的问题,假设以下代码。请忽略代码可能的低效率,我只对线程部分感兴趣。

//code without thread use
public static int getNextPrime(int from) {
    int nextPrime = from+1;
    boolean superPrime = false;
    while(!superPrime) {
        boolean prime = true;
        for(int i = 2;i < nextPrime;i++) {
            if(nextPrime % i == 0) {
                prime = false;
            }
        }
        if(prime) {
            superPrime = true;
        } else {
            nextPrime++;
        }
    }
    return nextPrime;
}

public static void main(String[] args) {
   int primeStart = 5;
   ArrayList list = new ArrayList();
   for(int i = 0;i < 10000;i++) {
       list.add(primeStart);
       primeStart = getNextPrime(primeStart);
   }
}

如果我正在运行这样的代码,大约需要56秒。但是,如果我有以下代码(作为替代):

public class PrimeRunnable implements Runnable {

    private int from;
    private int lastPrime;

    public PrimeRunnable(int from) {
        this.from = from;
    }

    public boolean isPrime(int number) {
        for(int i = 2;i < from;i++) {
            if((number % i) == 0) {
                return false;
            }
        }
        lastPrime = number;
        return true;
    }

    public int getLastPrime() {
        return lastPrime;
    }

    public void run() {
        while(!isPrime(++from))
            ;
    }
}

public static void main(String[] args) {
   int primeStart = 5;
   ArrayList list = new ArrayList();
   for(int i = 0;i < 10000;i++) {
     PrimeRunnable pr = new PrimeRunnable(primeStart);
     Thread t = new Thread(pr);
     t.start();
     t.join();
     primeStart = pr.getLastPrime();
     list.add(primeStart);
   }
}

整个操作大约需要7秒钟。我几乎可以肯定,即使我一次只创建一个线程,一个线程并不总是在创建另一个线程时完成。是对的吗?我也很好奇:为什么操作结束得这么快?

当我加入一个线程时,其他线程是否继续在后台运行,或者连接的线程是唯一正在运行的线程?

5 个答案:

答案 0 :(得分:3)

通过将join()放在循环中,您将启动一个线程,然后在运行下一个线程之前等待该线程停止。我想你可能想要更像这样的东西:

public static void main(String[] args) {
   int primeStart = 5;

   // Make thread-safe list for adding results to
   List list = Collections.synchronizedList(new ArrayList());

   // Pull thread pool count out into a value so you can easily change it
   int threadCount = 10000;
   Thread[] threads = new Thread[threadCount];

   // Start all threads
   for(int i = 0;i < threadCount;i++) {
     // Pass list to each Runnable here
     // Also, I added +i here as I think the intention is 
     //    to test 10000 possible numbers>5 for primeness - 
     //    was testing 5 in all loops
     PrimeRunnable pr = new PrimeRunnable(primeStart+i, list);
     Thread[i] threads = new Thread(pr);
     threads[i].start();  // thread is now running in parallel
   }

   // All threads now running in parallel

   // Then wait for all threads to complete
   for(int i=0; i<threadCount; i++) {
     threads[i].join();
   }
}

顺便说一下,pr.getLastPrime()在没有素数的情况下会返回0,所以你可能希望在将它添加到列表之前将其过滤掉。 PrimeRunnable必须吸收添加到最终结果列表的工作。另外,我认为PrimeRunnable实际上已经被其中的增量代码破坏了。我认为这是固定的,但我实际上并没有编译它。

public class PrimeRunnable implements Runnable {    
    private int from;
    private List results;   // shared but thread-safe

    public PrimeRunnable(int from, List results) {
        this.from = from;
        this.results = results;
    }

    public void isPrime(int number) {
        for(int i = 2;i < from;i++) {
                if((number % i) == 0) {
                        return;
                }
        }
        // found prime, add to shared results
        this.results.add(number);
    }

    public void run() {
        isPrime(from);      // don't increment, just check one number
    }    
}

并行运行10000个线程不是一个好主意。创建一个合理大小的固定线程池并让它们从共享队列中拉出工作是一个更好的主意。基本上每个工作人员从同一队列中提取任务,对其进行处理并将结果保存在某处。与Java 5+最接近的端口是使用由线程池支持的ExecutorService。您还可以使用将ExecutorService与结果队列组合在一起的CompletionService。

ExecutorService版本如下所示:

public static void main(String[] args) {
   int primeStart = 5;

   // Make thread-safe list for adding results to
   List list = Collections.synchronizedList(new ArrayList());

   int threadCount = 16;  // Experiment with this to find best on your machine
   ExecutorService exec = Executors.newFixedThreadPool(threadCount);

   int workCount = 10000;  // See how # of work is now separate from # of threads?
   for(int i = 0;i < workCount;i++) {
     // submit work to the svc for execution across the thread pool 
     exec.execute(new PrimeRunnable(primeStart+i, list));
   }

   // Wait for all tasks to be done or timeout to go off
   exec.awaitTermination(1, TimeUnit.DAYS);
}

希望能给你一些想法。我希望最后一个例子看起来比第一个好很多。

答案 1 :(得分:2)

您可以通过使第一个示例中的确切代码与线程一起运行来更好地测试这一点。用你的主要方法:

    private static int currentPrime;
public static void main(String[] args) throws InterruptedException {
    for (currentPrime = 0; currentPrime < 10000; currentPrime++) {
        Thread t = new Thread(new Runnable() {
            public void run() {
                getNextPrime(currentPrime);
            }});
        t.run();
        t.join();
    }
}

这将与原始版本同时运行。

回答你的“加入”问题:是的,当你使用“join”时,其他线程可以在后台运行,但在这种特殊情况下,你一次只能有一个活动线程,因为你阻止了创建新线程直到最后一个线程执行完毕。

答案 2 :(得分:2)

JesperE是对的,但我不相信只给出提示(至少在教室外):

请注意非线程版本中的此循环:

for(int i = 2;i < nextPrime;i++) {
  if(nextPrime % i == 0) {
    prime = false;
  }
}

与线程版本相反:

for(int i = 2;i < from;i++) {
  if((number % i) == 0) {
    return false;
  }
}

第一个循环将始终完全运行,而第二个循环将在找到除数时提前退出。

你可以通过添加如下的break语句使第一个循环也提前退出:

for(int i = 2;i < nextPrime;i++) {
  if(nextPrime % i == 0) {
    prime = false;
    break;
  }
}

答案 3 :(得分:1)

仔细阅读您的代码。这两种情况并没有做同样的事情,它与线程无关。

当你加入一个线程时,其他线程将在后台运行,是的。

答案 4 :(得分:0)

运行测试,第二个测试似乎不需要花费9秒 - 实际上,它至少需要花费第一个时间(这是预期的,因为线程无法帮助你实现它的实现方式示例

Thread.join只会在thread.joined终止时返回,然后当前线程将继续,你调用join的那个将会死掉。

快速参考 - 在开始一次迭代时思考线程并不依赖于前一次迭代的结果。