我有一个多线程程序,它似乎并没有真正利用我机器中的所有内核。这是代码,任何输入将不胜感激。
主类
public class MainClass{
public static void main(String [] args){
Work work=new Work();
work.doIt();
}
}
第二个类创建任务并将它们交给执行器服务,这里是伪代码
public class Work{
public void doIt() throws InterrputedException, Exception{
map=get some data and put it in the map;
ArrayList<Future<Integer>> list=new ArrayList<Future<Integer>>();
ArrayList<WorkCallable>jobs=new ArrayList<WorkCallable>();
for each entry in the map;
jobs.add(new WorkCallable(entry);
int numCores=Runtime.getRuntime().availableProcessors();
ExecutorService executor=Executors.newFixedThreadPool(numCores);
int size=jobs.size();
for(int i=0;i<size;i++){
Callable<Integer> worker=jobs.get(i);
Future<Integer> submit=executor.submit(worker);
list.add(submit);
}
executor.shutdown();
while(!executor.isTerminated()) {}
do something with the returned data;
}
}
可调用类
public class WorkCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception{
Properties props=new Properties();
props.put("annotators", "tokenize, ssplit, pos");
StanfordCoreNLP pipeline=new StanfordCoreNLP(props);
for(String id:entry.keySet()){
Annotation document=new Annotation(entry.get(id));
pipeline.annotate(document);
process the data;
return an integer value;
}
}
问题是,当我检查有多少线程正在运行时,我发现只有很少的线程,而且似乎执行器没有利用理想内核的优势!
我希望描述清楚。
更新:
- 使用的库是 StanfordCoreNLP 包,用于处理传递给 Callable 对象的文本作为 documentID 及其内容的映射。处理数据不是问题,因为我可以在不包含 StanfordCoreNLP 库的情况下正常工作。换句话说,文档的浅层处理工作正常,并利用所有核心。但是当我包含这个包时,它没有。
如果您使用的是Windows,那么JVM将委托线程来剥离NT内核。 POSIX 类型的操作系统将操作系统线程直接映射到 JVM,并协同调度。
但是,无论发生什么情况,您都无法确保线程在内核/处理器之间均匀分配。 当您启动第 4 个线程时,操作系统上的其他内容可能在核心 4 上运行,因此它可能会被调度到另一个核心。 或者调度程序可以决定将它们堆叠在同一内核上。
在这一点上,根据您提供的信息,我怀疑线程之间存在一些争用,因此很可能某些线程被阻塞/等待。 要验证这一点,您可以使用 JVisual VM 并获取线程转储(Jconsole 也是一个选项)。JVisual VM是监控java应用程序的实用程序,并带有JDK。如果您之前没有使用它,这将是您学习它的很好的时间投资,因为它非常有用且易于使用。
请参阅此处的JVisualVM
- 使用 JVisual VM 获取线程转储连接到您的程序。
- 它会为你提供程序中的线程状态时间实例,如果存在争用和/或阻塞,则很容易 使用线程转储进行点位。
- 随意将其粘贴到此处,以防您无法弄清楚是什么发生在线程转储中,尽管有许多资源您了解网络上的线程转储
另一方面,正如@Marko所指出的,您可以更有效地处理执行程序关闭,我会说ExecutorCompletionService将满足您的要求并使代码更加优雅和易于阅读。查看此处的执行器完成服务 一旦您确定空闲内核可能是,您可以重构以使用 ECS。