【问题标题】:Service.restart() generates an huge unwanted amount of threadsService.restart() 生成大量不需要的线程
【发布时间】:2015-11-03 20:45:18
【问题描述】:

当用户在 TextField 中输入时:我需要通过 WebRequest 搜索一些数据并显示出来。

当用户输入多个字符时,应取消之前的下载并开始新的下载。

所以我使用一个任务来下载数据,并在任务返回数据时使用一个服务来显示数据。

s = new Service(){
    @Override
    protected Task createTask() {
        return new Task<String>(){
            @Override
            protected String call() throws Exception {
                //DOWNLOAD DATA
                System.out.println("1");
                Thread.sleep(1000);
                System.out.println("2");
                Thread.sleep(1000);
                System.out.println("3");
                Thread.sleep(1000);
                return "banana";
            }
        };
    }
};
s.setOnSucceeded(new EventHandler<WorkerStateEvent>(){
    @Override
    public void handle(WorkerStateEvent event) {
        System.out.println(event.getSource().getValue() + " DISPLAYED");
    }
});

//HANDLE KEY RELEASED ON A TEXTFIELD
public void onTextFieldKeyReleased() {
    s.restart();
}

我注意到每次重新启动服务时都会出现另一个线程,直到出现一定数量的线程。 这使得程序显示数据有很大的延迟。

当 KeyReleasedEvent 发生时,我希望服务执行的操作是取消当前正在运行的任务并开始一个新任务……而不是每次都附加一个新任务。

我怎样才能做到这一点?

【问题讨论】:

  • 我无法用您发布的代码重现“巨大延迟”。你能创建一个minimal reproducible example吗?
  • @James_D 也许您无法重现延迟,但您可以看到创建了很多线程,我也不希望这种情况发生。另外,我注意到在打印每个线程之前必须关闭生成的服务,这需要一秒钟,这对我来说太多了。
  • 我不明白为什么创建一个新线程会导致问题,但是你读过documentation吗? “在打印每个线程之前必须关闭生成的服务”是什么意思?我不明白。即使您不发布演示延迟的代码,您至少可以使用演示该问题的代码来编辑您的问题吗?
  • @James_D “调用 Thread.sleep() 成本不高,但创建线程可能会。”这就是我使用服务的原因。无论如何......我至少在现在解决了这种情况,ScheduledExecutorService 使用 newSingleThreadScheduledExecutor 初始化。这个执行器不是创建很多线程,而是一次只运行一个线程/
  • 我会假设服务的默认执行器缓存线程并尽可能在现有(未使用的)线程上运行新任务(即默认情况下它可能使用缓存的线程池执行器)。无论如何,您有什么实际证据证明它正在创建这么多线程?

标签: java multithreading javafx task java.util.concurrent


【解决方案1】:

我无法重现你提到的延迟。

作为documentedService 使用“一些默认执行程序”来执行由createTask 方法创建的Tasks。如果要修改默认行为,可以配置执行器,例如:

s.setExecutor(Executors.newCachedThreadPool(runnable -> {
    Thread t = new Thread(runnable);
    t.setDaemon(true);
    return t ;
});

显然,您可以使用您选择的任何执行器,具体取决于您的确切要求(您的问题完全不清楚)。例如,如果你想限制(比如说)5 个线程,你可以这样做

s.setExecutor(Executors.newFixedThreadPool(5, runnable -> {
    Thread t = new Thread(runnable);
    t.setDaemon(true);
    return t ;
});

这是一个 SSCCE。我添加了一些日志记录来跟踪服务的状态并显示用于任务的线程:

import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class ServiceRestartTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        Service<Void> s = new Service<Void>(){
            @Override
            protected Task<Void> createTask() {
                return new Task<Void>(){
                    @Override
                    protected Void call() throws Exception {
                        //DOWNLOAD DATA
                        System.out.println("New task on thread "+Thread.currentThread());
                        System.out.println("1");
                        Thread.sleep(1000);
                        System.out.println("2");
                        Thread.sleep(1000);
                        System.out.println("3");
                        Thread.sleep(1000);
                        return null;
                    }
                };
            }
        };
        s.setExecutor(Executors.newCachedThreadPool(runnable -> {
            Thread t = new Thread(runnable);
            t.setDaemon(true);
            return t;
        }));

        s.stateProperty().addListener((obs, oldState, newState) -> System.out.println(newState));

        TextField textField = new TextField();
        textField.setOnKeyReleased(e -> s.restart());

        primaryStage.setScene(new Scene(new StackPane(textField), 350, 120));
        primaryStage.show();

    }

    public static void main(String[] args) {
        launch(args);
    }
}

输出,只显示两个线程被使用:

预定的 线程 Thread[Thread-5,5,main] 上的新任务 1 跑步 取消 准备好 预定的 线程 Thread[Thread-6,5,main] 上的新任务 1 跑步 取消 准备好 预定的 线程 Thread[Thread-6,5,main] 上的新任务 1 跑步 取消 准备好 预定的 线程 Thread[Thread-6,5,main] 上的新任务 1 跑步 取消 准备好 预定的 线程 Thread[Thread-6,5,main] 上的新任务 1 跑步 取消 准备好 预定的 线程 Thread[Thread-6,5,main] 上的新任务 1 跑步 2 3 成功

【讨论】:

  • 似乎将执行器设置为 Executors.newSingleThreadScheduledExecutor() 解决了线程过多的问题
  • 我尝试了您的版本,但在我的情况下它不起作用,无论如何感谢您帮助我以正确的方式做事。如果你编辑你的答案,我会投票。
  • 您可能不需要预定的执行程序(您的问题中至少没有关于调度的问题)。如果你真的需要两件事异步发生,单线程执行器会导致问题。缓存线程池复用线程:我这里只看到创建了两个线程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
  • 1970-01-01
  • 1970-01-01
  • 2012-02-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多