我正在编写一款Java程序,由于它所执行的任务特性,它需要大量的CPU资源。不过,其中许多部分可以并行运行,并且我已经将我的程序设计成多线程的形式。但是,当我运行它时,似乎只使用了一个CPU,直到需要更多的CPU资源时才使用其他CPU - 那么,在Java中有没有办法强制不同的线程在不同的核心/ CPU上运行呢?
我正在编写一款Java程序,由于它所执行的任务特性,它需要大量的CPU资源。不过,其中许多部分可以并行运行,并且我已经将我的程序设计成多线程的形式。但是,当我运行它时,似乎只使用了一个CPU,直到需要更多的CPU资源时才使用其他CPU - 那么,在Java中有没有办法强制不同的线程在不同的核心/ CPU上运行呢?
还有另一个问题:控制工作很难!一个好的实践是有一个管理线程来创建和提交任务,然后有几个带有工作队列的工作线程(使用ExecutorService)。
我只是简要介绍了关键点--许多专家认为,多线程编程是最难的编程主题之一。 它是非直观的、复杂的,而且抽象通常很弱。
编辑 -- 使用 ExecutorService 的示例:
public class TaskThreader {
class DoStuff implements Callable {
Object in;
public Object call(){
in = doStep1(in);
in = doStep2(in);
in = doStep3(in);
return in;
}
public DoStuff(Object input){
in = input;
}
}
public abstract Object doStep1(Object input);
public abstract Object doStep2(Object input);
public abstract Object doStep3(Object input);
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
ArrayList<Callable> tasks = new ArrayList<Callable>();
for(Object input : inputs){
tasks.add(new DoStuff(input));
}
List<Future> results = exec.invokeAll(tasks);
exec.shutdown();
for(Future f : results) {
write(f.get());
}
}
}
FixedThreadPool
似乎很好,因为它限制了正在运行的线程数量,(1)避免任务变化导致负载过重,(2)确保一些线程先完成(并快速获得一些结果)。这对于进行实验非常有用。 - toto_tico首先,你需要证明你的程序在多个核心上运行会更快。许多操作系统会尽可能地将程序线程运行在同一核心上。
在同一核心上运行有很多优点。CPU缓存是热的,这意味着该程序的数据已加载到CPU中。锁定/监视/同步对象在CPU缓存中,这意味着其他CPU不需要在总线上执行缓存同步操作(代价高昂!)。
可以非常容易地使您的程序始终在同一CPU上运行的一个问题是过度使用锁定和共享内存。您的线程不应相互通信。线程越不经常在同一内存中使用相同的对象,它们就越经常在不同的CPU上运行。它们越经常使用相同的内存,它们就越经常必须阻塞等待其他线程。
每当操作系统看到一个线程为另一个线程阻塞时,它将尽可能在同一CPU上运行该线程。这减少了在CPU之间移动的内存量。我猜这就是导致您程序中出现的情况。
首先,我建议阅读"Java并发编程实战" by Brian Goetz。
这是迄今为止最好的描述并发Java编程的书籍。在最低层次上,可以 创建和销毁线程。Java使得以可移植的跨平台方式创建线程变得容易。
由于频繁地创建和销毁线程往往会变得昂贵,因此Java现在包括Executors来创建可重用的线程池。任务可以分配给执行器,并且可以通过Future对象检索结果。
通常,人们有一个任务可以分成更小的任务,但最终结果需要合并在一起。例如,在归并排序中,可以将列表分成越来越小的部分,直到每个核心都进行排序。但是,由于每个子列表都已排序,因此需要合并它们以获得最终排序列表。由于这种“分而治之”的问题相当普遍,因此有一个JSR框架可以处理底层的分发和连接。这个框架可能会包含在Java 7中。在Java中没有设置CPU亲和力的方法。
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4234402如果必须要设置,可以使用JNI创建本地线程并设置它们的亲和力。
您可以使用Java 8版本中的Executors API。
public static ExecutorService newWorkStealingPool()
创建了一个工作偷懒的线程池,使用所有可用的处理器作为其目标并行度水平。
由于工作偷懒的机制,空闲线程从忙碌线程的任务队列中窃取任务,整体吞吐量将增加。
从grepcode看,newWorkStealingPool
的实现如下:
/**
* Creates a work-stealing thread pool using all
* {@link Runtime#availableProcessors available processors}
* as its target parallelism level.
* @return the newly created thread pool
* @see #newWorkStealingPool(int)
* @since 1.8
*/
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
最简单的方法是将程序分成多个进程。操作系统会将它们分配到各个核心上。
稍微困难一些的方法是将程序分成多个线程,并信任JVM适当地分配它们。这通常是人们利用可用硬件的方式。
编辑
一个多进程程序如何变得“更容易”?这是管道中的一步。
public class SomeStep {
public static void main( String args[] ) {
BufferedReader stdin= new BufferedReader( System.in );
BufferedWriter stdout= new BufferedWriter( System.out );
String line= stdin.readLine();
while( line != null ) {
// process line, writing to stdout
line = stdin.readLine();
}
}
}
管道中的每个步骤都具有类似的结构。包括任何处理在内,需要9行开销。
这可能不是绝对最有效的方法。但它非常简单。
您的并发进程的整体结构不是JVM问题,而是操作系统问题,因此请使用Shell。
java -cp pipline.jar FirstStep | java -cp pipline.jar SomeStep | java -cp pipline.jar LastStep
ObjectInputStream
和ObjectOutputStream
替换BufferedReader
和BufferedWriter
来实现这一点。我认为这个问题与Java并行处理框架(JPPF)有关。使用它,您可以在不同的处理器上运行不同的作业。
http://ch.sun.com/sunnews/events/2009/apr/adworkshop/pdf/5-1-Java-Performance.pdf