Java 未在命令行应用程序中调用SwingWorker.process()
我观察到调用SwingWorkers的命令行应用程序有一种奇怪的行为。代码是非最优的,因为它创建了很多线程池。但是,由于Java 未在命令行应用程序中调用SwingWorker.process(),java,multithreading,command-line,deadlock,swingworker,Java,Multithreading,Command Line,Deadlock,Swingworker,我观察到调用SwingWorkers的命令行应用程序有一种奇怪的行为。代码是非最优的,因为它创建了很多线程池。但是,由于生成变量的控制,除最后一个池外,所有这些池都不执行任何代码。这意味着这些池中的线程不仅不参与锁竞争,还应该被垃圾收集并消失 下面是一个最简单的工作示例(没有使任何内容变得有用): package test; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.
生成
变量的控制,除最后一个池外,所有这些池都不执行任何代码。这意味着这些池中的线程不仅不参与锁竞争,还应该被垃圾收集并消失
下面是一个最简单的工作示例(没有使任何内容变得有用):
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();
}
}
}
观察到的行为:输出包括启动1\n[…]启动1034\n之后,进程仍处于活动状态,但不再打印更多行。在死锁时刻,我的进程的线程数是31014。实验是在一台24芯的机器上进行的
预期行为:对于k=1,2,…,进程应保持从k\n开始打印。。。永久或抛出由创建的线程池过多导致的OutOfMemoryError
给出的示例具有有限的调试功能。在某个时刻,我有更多的printf命令,它们意味着当前一代的所有创建线程都调用了它们的
publish()
方法,但EDT没有调用process()
方法时会发生死锁。与Holger讨论后,问题似乎是由创建多个线程池引起的:
在程序执行k轮后(即打印开始k\n
),创建了大小为34的k个线程池。其中,除了执行上一代的doInBackground()
方法的30个线程之外,所有线程都不执行任何代码,但仍在运行。仍在执行代码的30个线程在get()
和put()
方法上同步,并陷入死锁,因为AWT eventdispatch线程出于某种原因不执行publish()
方法。造成这种死锁的唯一原因是有许多线程被创建并运行(尽管它们中的大多数都不是活动线程,也没有参与竞争)
讨论后的共识似乎是:由于创建了太多线程,系统(Java外部)违反了一些约束,使JVM停止运行。您期望什么?当this.done==this.size
时,调用run()
,将done
设置为零。只有当this.done>this.size
时,才会打印消息,但如果在达到该条件之前将done
设置为零,则显然不可能达到该条件。我不确定您在重复创建新池时是否理解线程池的用途…但这段奇怪代码的用途仍然不清楚。您在创建新线程池之前打印“Start…”消息,因此每次看到该消息,您正在创建一个固定数量为15个线程的新线程池。因此,当您看到消息“Starting 2813”时,您已经创建了2813×15==42195个线程。你认为,你真的可以排除程序挂起和创建42195个线程之间的关系,所有线程都在同一个对象上重复调用synchronized
方法?抱歉,如果这听起来太苛刻,但是一个问题的最低要求是描述预期的行为,不可能从你的程序中猜出意图,因为它根本没有任何意义。所有线程都在更改相同的变量,因此线程是否完成完全是随机的。由于每次完成都会触发另外15个线程的开始,所以您只需创建臭名昭著的fork bomb的一个变体。我通过发布另一个解释来反驳自己:您假设,当一个线程遇到this.done==this.size
条件时,所有其他线程都已完成。这是错误的。将有一个线程遇到get
中的最后一项。此时,没有其他线程遇到left==0
。线程将发布最后一项。现在,有15个线程调用get
,再加上EDT调用put
来竞争锁。有些线程可能会成功并遇到left==0
,但肯定不是全部。现在EDT遇到get()
中的this.done==this.size
,调用run()
,启动15个新线程,并将非常左侧的变量重置为零,尚未完成的线程将在下一次get
调用中读取该变量。他们都像新来的一样。直到遇到get
中的最后一项,比赛再次开始。请注意,synchronized
是“不公平的”。由于发布
/进程
关系,EDT最后尝试获取锁的几率更高,使更多池线程处于运行状态。您看错了方向。如果EDT停止调用进程()。这意味着,一旦垃圾收集器回收了一些池,其他线程应该继续并最终进入等待状态,甚至终止(这可能取决于Java版本,池是否有这样的清理)。可能是EDT在尝试为新池创建更多线程时挂起。这将取决于系统,并且超出垃圾收集器的范围。我承认,我没有尝试过你的24核程序……我在第一篇文章中修改了代码,以保证在连续几代中创建的线程不会相互竞争。新代码运行了1034代,并出现死锁。您看到了为什么会出现死锁而不是异常的任何解释了吗?您仍然在创建新的线程池,即使您现在正在存储
Tester tester = new Tester(30 * 400, 30);
tester.run();