我观察到一个调用SwingWorkers的命令行应用程序的奇怪行为。从代码创建大量线程池的角度来看,代码是非最优的。但是,由于generation
变量的控制,除最后一个之外的所有这些池都不执行任何代码。这意味着来自这些池的线程不仅不参与锁的竞争,而且还应该被垃圾收集并消失。
最小的工作示例(没有任何有用的东西)如下:
package test;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.SwingWorker;
public class Tester {
private final int threads;
private ExecutorService threadPool;
private final int size;
private long currGen;
private int left;
private int done;
public Tester(int size, int threads) {
this.threads = threads;
this.size = size;
this.currGen = 0;
this.left = size;
this.done = 0;
}
private class TestWorker extends SwingWorker<Object, Object> {
private final Tester tester;
private final long generation;
TestWorker(Tester tester, long generation) {
this.tester = tester;
this.generation = generation;
}
@Override
protected Object doInBackground() throws Exception {
while(this.tester.get(generation)) {
Thread.sleep(1);
publish(1);
}
return null;
}
@Override
protected void process(List<Object> results) {
for(Object n : results) {
this.tester.put(generation);
}
}
}
public void run() {
this.currGen++;
this.left = size;
this.done = 0;
System.out.printf("Starting %d\n", currGen);
this.threadPool = Executors.newFixedThreadPool(threads + 4);
for (int threadId = 0; threadId < threads; threadId++) {
threadPool.submit(new TestWorker(this, currGen));
}
}
public synchronized boolean get(long generation) {
if (generation != currGen) {
return false;
}
if (this.left == 0) {
return false;
}
this.left--;
return true;
}
public synchronized void put(long generation) {
if (generation != currGen) {
return;
}
this.done++;
if (this.done == this.size) {
this.run();
}
}
}
然后,这个类在我的程序的main方法中运行:
Tester tester = new Tester(30 * 400, 30);
tester.run();
观察到的行为:输出包括Starting 1 \ n [...] Starting 1034 \ n之后,该过程仍处于活动状态,但不再打印任何行。在发生死锁时,我的进程的线程数为31014。该实验在24芯机器上进行。
预期的行为:进程应该继续打印k = n for k = 1,2,...永远或者因创建的ThreadPools太多而引发OutOfMemoryError
。
所提供的示例调试有限。在某些时候我有更多的printf命令,它们暗示当当前一代的所有创建线程都调用了他们的publish()
方法但是EDT没有调用process()
方法时发生死锁。
答案 0 :(得分:0)
在与Holger讨论之后,问题似乎是由创建多个ThreadPool引起的:
程序执行了k轮(即打印Starting k\n
)后,创建了大小为34的k个ThreadPools。除此之外,除了上一代执行doInBackground()
方法的30个以外的所有线程最后都没有执行任何代码,但仍然是running
。仍然执行代码的30个线程在get()
和put()
方法上同步并陷入死锁,因为AWT-eventdispatch线程由于某种原因不执行publish()
方法。这种僵局是由于创建了很多线程并且running
(尽管大多数线程不活跃且不参与竞赛)这一事实造成的。
讨论后的共识似乎是:由于创建了太多线程,系统(Java外部)违反了某些约束,并使JVM停止进展。