我有一个过程需要并行计算许多小任务,然后按照任务的自然顺序处理结果。为此,我有以下设置:
一个简单的ExecutorService,以及一个阻塞队列,用于在将Callable提交给执行程序时保留Future对象:
ExecutorService exec = Executors.newFixedThreadPool(15);
LinkedBlockingQueue<Future<MyTask>> futures = new LinkedBlockingQueue<Future<MyTask>>(15 * 64);
一些调试代码,用于计算已提交的数量和已处理任务的数量,并定期将其写出(注意processed
在任务代码本身的末尾递增):
AtomicLong processed = new AtomicLong(0);
AtomicLong submitted = new AtomicLong(0);
Timer statusTimer = new Timer();
statusTimer.schedule(new TimerTask() {
@Override
public void run() {
l.info("Futures: " + futures.size() + "; Submitted: " + submitted.get() + "; Processed: " + processed.get() + "; Diff: " + (submitted.get() - processed.get())));
}
}, 60 * 1000, 60 * 1000);
从队列(实际上是生成器)获取任务并将它们提交给执行程序的线程,将生成的Future放入futures
队列(这就是我确保不提交太多的任务耗尽内存):
Thread submitThread = new Thread(() ->
{
MyTask task;
try {
while ((task = taskQueue.poll()) != null) {
futures.put(exec.submit(task));
submitted.incrementAndGet();
}
} catch (Exception e) {l .error("Unexpected Exception", e);}
}, "SubmitTasks");
submitThread.start();
当前线程然后take
完成了futures
队列中的任务并处理结果:
while (!futures.isEmpty() || submitThread.isAlive()) {
MyTask task = futures.take().get();
//process result
}
当我在具有8个内核的服务器上运行它时(注意代码当前使用15个线程),CPU利用率仅达到约60%。我看到我的调试输出如下:
INFO : Futures: 960; Submitted: 1709710114; Processed: 1709709167; Diff: 947
INFO : Futures: 945; Submitted: 1717159751; Processed: 1717158862; Diff: 889
INFO : Futures: 868; Submitted: 1724597808; Processed: 1724596954; Diff: 853
INFO : Futures: 940; Submitted: 1732030120; Processed: 1732029252; Diff: 871
INFO : Futures: 960; Submitted: 1739538576; Processed: 1739537758; Diff: 818
INFO : Futures: 960; Submitted: 1746965761; Processed: 1746964811; Diff: 950
线程转储显示许多线程池线程阻塞如下:
"pool-1-thread-14" #30 prio=5 os_prio=0 tid=0x00007f25c802c800 nid=0x10b2 waiting on condition [0x00007f26151d5000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00007f2fbb0001b0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:897)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:439)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
我对调试输出的解释是,在任何给定的时间点,我至少有几百个任务已提交给执行程序服务,但尚未处理(我也可以在堆栈跟踪中确认SubmitTasks线程在LinkedBlockingQueue.put
上被阻止。然而,堆栈跟踪(以及服务器利用率统计信息)向我显示Executor服务在LinkedBlockingQueue.take上被阻止(我假设内部任务队列为空)。
我读错了什么?
答案 0 :(得分:0)
涉及BlockingQueue
的线程总是很棘手。只需查看代码而无需使用您所使用的比例运行。我有一些建议。像Jessica Kerr这样的业内许多专家建议你永远不要永远阻止。你可以做的是在LinkedBlockingQueue中使用带有超时的方法。
Thread submitThread = new Thread(() ->
{
MyTask task;
try {
while ((task = taskQueue.peek()) != null) {
boolean success = futures.offer(exec.submit(task), 1000, TimeUnit.MILLISECONDS);
if(success) {
submitted.incrementAndGet();
taskQueue.remove(task);
}
}
} catch (Exception e) {l .error("Unexpected Exception", e);}
}, "SubmitTasks");
submitThread.start();
还有。
while (!futures.isEmpty() || submitThread.isAlive()) {
Future<MyTask> f = futures.poll(1000, TimeUnit.MILLISECONDS);
if(f != null) {
MyTask task = f.get();
}
//process result
}
观看Jessica Kerr在Concurrency tools in JVM
上观看此视频答案 1 :(得分:0)
经过多次更改和测试后,我最终将任务分组为10000个组(即每个Future
负责一组10000 MyTask
个任务,而不仅仅是1个) 。这样ExecutorService
每秒执行大约10-20个任务(而不是相当高的100000-200000我是&#34;要求&#34;它要做。这种方法显着提高了速度并导致了一个完整的100%的CPU利用率。
事后看来,它似乎是不合理的&#34;每秒执行超过10万个任务。我读到的是在并发管理/锁定开销和上下文切换(猜想)上花费了太多时间。