我有以下代码:
public class Cancelling {
public static void main(String args[]) {
ToBeCancelled tbc = new ToBeCancelled();
ForkJoinPool pool = new ForkJoinPool(1);
Future<?> future = pool.submit(tbc);
try {
Thread.sleep(3000);
} catch (InterruptedException ie) {}
future.cancel(true);
if (future.isCancelled())
System.out.println("The task has been cancelled");
}
}
ToBeCancelled
类为:
public class ToBeCancelled implements Runnable {
public void run() {
try {
Thread.sleep(5000); // should throw exception here
} catch (Exception e) {
return; // should exit
}
System.out.println("I should never be able to print this");
}
}
主线程应该启动,等待3秒,然后用future.cancel(true)
取消ToBeCancelled
任务。然后,它应该打印The task has been cancelled
,而任务永远不会打印它的消息。至少,当我从控制台启动它时是这样的。
当我从带有TextArea的GUI应用程序启动它时(输出被重定向到TextArea),情况并非如此。主方法打印The task has been cancelled
,但任务也打印I should never be able to print this
。
这快把我逼疯了。根据我的理解,任务应该在Thread.sleep(5000)
方法上接收其cancel
命令,这将触发一个异常,从而捕获并使线程返回。但这并没有发生,而且主要认为它已经被取消了。这就像cancel
方法被任务完全忽略了。
我已经尝试了我能想到的一切,检查cancel
的返回值,使任务等待更长时间,使用Thread.currentThread().isInterrupted()
,但没有任何作用。
如果有人认为它可能是GUI应用程序上的东西,这是启动程序的方法:
public static void StartProgram(String name) {
try {
Method m = Class.forName(name).getDeclaredMethod("main",String[].class);
Object[] args = new Object[1];
String s[] = new String[2];
s[0] = tf1.getText();
s[1] = tf2.getText();
args[0] = s;
t = new Thread(new ProgramStarter(args, m));
t.start();
} catch (Exception e) {
e.printStackTrace();
}
}
与ProgramStarter
为:
public class ProgramStarter implements Runnable {
private Object[] args;
private Method m;
public ProgramStarter(Object args[], Method m) {
this.args = args;
this.m = m;
}
public void run() {
try {
m.invoke(null, args);
} catch (Exception e) {
e.printStackTrace();
}
}
}
问题是你的验证是错误的。您认为您的代码在从控制台运行时可以正常工作,但实际上,它在所有情况下都失败了。当从控制台运行时,主线程在尝试取消future之后结束,JVM将终止,因为JVM中只剩下守护线程。由于JVM终止,您没有注意到取消不起作用。
当在main
方法的末尾添加sleep
以延迟JVM终止时,您将注意到在从控制台运行时也会打印"I should never be able to print this"
。因此,GUI和控制台版本之间的唯一区别是正在运行的事件调度线程阻止JVM终止,因此您可以看到它无法工作。
底线是:不要使用 ForkJoinPool
,除非你有这样做的理由。
因为您只想让submit
成为一个简单的单后台线程执行器,所以您可以使用Executors.newFixedThreadPool(1)
创建执行器。这有更少的意外行为:默认情况下,它的线程是非守护进程,它的Future
将cancel
与中断预期。