当我发现在运行几次后ScheduledService停止调度时,我正在使用JavaFX应用程序。
我找不到任何明显的原因。当我跟踪ScheduledService的状态时,似乎它切换到了SCHEDULED状态,然后变得无声。
我将代码减少到几乎没有,以期缩小问题范围。我发现,当我ScheduledService创建anonymos类的Task时,不会发生此问题,但是当我使用子类或顶级类时,会发生此问题。
package application;
import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.stage.Stage;
public class MyApplication extends Application
{
private static volatile int counter;
@Override
public void start( Stage primaryStage )
{
ScheduledService<Void> svc = new ScheduledService<>()
{
@Override
protected Task<Void> createTask()
{
return new MyTask();// if i use new Task<Void>{...} it works fine
}
};
svc.start();
}
private static class MyTask extends Task<Void>
{
@Override
protected Void call() throws Exception
{
System.out.println( "MyTask#call() " + counter++ );
return null;
}
}
public static void main( String[] args )
{
launch( args );
}
}
输出
MyTask#call() 0
MyTask#call() 1
MyTask#call() 2
MyTask#call() 3
MyTask#call() 4
MyTask#call() 5
MyTask#call() 6
MyTask#call() 7
MyTask#call() 8
MyTask#call() 9
MyTask#call() 10
MyTask#call() 11
MyTask#call() 12
MyTask#call() 13
MyTask#call() 14
这是一个错误吗?
我在用着
Windows 10
OracleOpen_jdk-12.0.1
javafx-sdk-12.0.1
最佳答案
我可以在以下环境中重现该问题:
稍稍修改代码即可了解问题所在:
import java.lang.ref.Cleaner;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.stage.Stage;
public class Main extends Application {
private static final Cleaner CLEANER = Cleaner.create();
private static final AtomicInteger COUNTER = new AtomicInteger();
@Override
public void start(Stage primaryStage) {
var service = new ScheduledService<Void>() {
@Override
protected Task<Void> createTask() {
return new MyTask();
}
};
CLEANER.register(service, () -> {
System.out.println("SERVICE GARBAGE COLLECTED!");
System.exit(0);
});
service.start();
}
private static class MyTask extends Task<Void> {
@Override
protected Void call() throws Exception{
System.out.println("MyTask#call() " + COUNTER.incrementAndGet());
return null;
}
}
}
以上输出:
MyTask#call() 1
MyTask#call() 2
MyTask#call() 3
MyTask#call() 4
MyTask#call() 5
MyTask#call() 6
MyTask#call() 7
MyTask#call() 8
MyTask#call() 9
MyTask#call() 10
MyTask#call() 11
MyTask#call() 12
MyTask#call() 13
MyTask#call() 14
MyTask#call() 15
MyTask#call() 16
MyTask#call() 17
MyTask#call() 18
MyTask#call() 19
SERVICE GARBAGE COLLECTED!
这样就可以解释发生了什么—
ScheduledService
实例正在被垃圾回收。这是有道理的,因为您不需要维护对ScheduledService
实例的强引用,也不会执行Task
实例。一旦ScheduledService
实例被垃圾回收,就无法为下一个执行周期安排另一个Task
。确实,非静态嵌套或匿名类维护对封闭类实例的引用。但是,封闭类是
Main
(代码中的MyApplication
),而不是匿名ScheduledService
类。使用
new Task<Void>() { ... }
起作用的原因是因为现在封闭的实例是ScheduledService
实例。ScheduledService
类使用内部java.util.Timer
实例来安排Task
以便以后执行。因此,在等待执行时,Task
使用的线程可以很强地到达Timer
。在执行时,执行它的线程可以很强地到达Task
(该线程来自Executor
属性中使用的ScheduledService#executor
)。因此,当使用由匿名
Task
括起来的匿名ScheduledService
时,最终可以实现ScheduledService
。奇怪的是,如果我添加代码来显示主要的
Stage
,则永远不会垃圾回收ScheduledService
。我不确定为什么会这样。即使使用Platform.setImplicitExit(false)
并关闭Stage
,也是如此。