2

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

//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 秒。我几乎可以肯定,即使我一次只创建一个线程,一个线程并不总是在创建另一个线程时完成。是对的吗?我也很好奇:为什么手术结束的这么快?

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

4

5 回答 5

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。您还可以使用 CompletionService 将 ExecutorService 与结果队列相结合。

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);
}

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

于 2008-10-30T01:38:13.433 回答
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();
    }
}

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

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

于 2008-10-29T21:27:14.110 回答
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;
  }
}
于 2008-10-29T21:45:39.533 回答
1

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

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

于 2008-10-29T21:13:29.753 回答
0

运行测试,第二个似乎不需要 9 秒——事实上,它至少和第一个一样长(这是意料之中的,threding 无法帮助它在您的示例中实现。

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

作为快速参考——在开始一个迭代时考虑线程并不依赖于前一个迭代的结果。

于 2008-10-29T21:18:29.993 回答