JavaFX的非阻塞线程安全计数器

时间:2018-11-30 08:00:24

标签: multithreading javafx javafx-8

我正在尝试实现一个线程安全的解决方案,以保持对已完成的成功任务的计数,这些任务最终将绑定到UI上显示的标签。但是,当我使用下面的AtomicInteger时,它会在任务开始运行时锁定我的UI,但是,如果我删除所有AtomicInteger引用,一切都会正常进行。有没有一种可以实现此目标的非阻塞线程安全方法?

public void handleSomeButtonClick(){
    if(!dataModel.getSomeList().isEmpty()) {
        boolean unlimited = false;
        int count = 0;
        AtomicInteger successCount = new AtomicInteger(0);

        if(countSelector.getValue().equalsIgnoreCase("Unlimited"))
            unlimited = true;
        else
            count = Integer.parseInt(countSelector.getValue());

        while(unlimited || successCount.get() < count) {
            Task task = getSomeTask();
            taskExecutor.submit(task);
            task.setOnSucceeded(event -> {
                if (task.getValue())
                    log.info("Successfully Completed Task | Total Count: " + successCount.incrementAndGet());
                else
                    log.error("Failed task");
            });
        }
    }
}

2 个答案:

答案 0 :(得分:1)

您的循环等待一定数量的任务完成。甚至可能是一个无限循环。

这不是一个好主意:

  • 您阻止了似乎是JavaFX应用程序线程的调用线程。
  • 您无法控制提交的任务数。 count可以是3,但是由于您仅计划循环中的任务,因此在第一个任务完成之前可以创建和计划1000个或更多任务。

此外,如果您使用onSucceeded / onFailed,则不需要使用AtomicInteger或任何类似类型的同步,因为这些处理程序都在JavaFX应用程序线程上运行。 / p>

您的代码可以这样重写:

private int successCount;

private void scheduleTask(final boolean unlimited) {
    Task task = getSomeTask();
    task.setOnSucceeded(event -> {
        // cannot get a Boolean from a raw task, so I assume the task is successfull iff no exception happens
        successCount++;
        log.info("Successfully Completed Task | Total Count: " + successCount);
        if (unlimited) {
            // submit new task, if the number of tasks is unlimited
            scheduleTask(true);
        }
    });
    // submit new task on failure
    task.setOnFailed(evt -> scheduleTask(unlimited));
    taskExecutor.submit(task);
}

public void handleSomeButtonClick() {
    if(!dataModel.getSomeList().isEmpty()) {
        successCount = 0;
        final boolean unlimited;
        final int count;

        if(countSelector.getValue().equalsIgnoreCase("Unlimited")) {
            unlimited = true;
            count = 4; // set limit of number of tasks submitted to the executor at the same time
        } else {
            count = Integer.parseInt(countSelector.getValue());
            unlimited = false;
        }

        for (int i = 0; i < count; i++) {
            scheduleTask(unlimited);
        }
    }
}

注意::此代码冒着在完成之前的任务之前多次单击handleButtonClick的风险。您应该避免在完成旧任务之前安排新任务,或者使用包含int的某种引用类型来代替计数,在handleSomeButtonClick中创建该对象并将该对象传递给scheduleTask

答案 1 :(得分:0)

您的问题是future.get()块并等待结果。 如果使用Vavr库,这将很简单。 因为它可以将代码附加到其将来,成功或失败后会自动运行。 因此,您不必等待。 这是一个使用Vavr未来的示例。

        CheckedFunction0<String> thisIsATask = () -> {
        if ( /*do something*/ ){
            throw new Exception("Hey");
        }
        return "ABC";
    };

    List<Future<String>> futureList = new ArrayList<>();

    for (int x = 0; x < 10; x++) {
        futureList.add(Future.of(getExecutorService(), thisIsATask));
    }

    futureList.forEach((task) -> {
        // This will run if success
        task.onSuccess(s -> s.equals("ABC") ? Platform.runLater(()->UpdateCounter()) : wtf());
        // Your get the exception if it is fail;
        task.onFailure(e -> e.printStackTrace());
        // task.onComplete() will run on any case when complete
    });

这不是阻塞,任务完成或捕获到异常时,onSucess onFailure或onComplete上的代码将运行。

注意:Future.of将使用您传入的executorService在新线程上运行每个任务,一旦完成任务,您在onSuccess上提供的代码将继续在该线程上运行,因此如果您调用javafx,请记住平台。 runLater()

如果您想在所有任务完成后运行某些程序,那么

    // the code at onComplete will run when tasks all done
    Future<Seq<String>> all = Future.sequence(futureList);
    all.onComplete((i) -> this.btnXYZ.setDisable(false));