java - 如何使用暂停、线程(任务、服务)模拟队列和堆栈 JavaFX

标签 java multithreading javafx

我一直在 YouTube、Stack-Overflow 和 fxexperience 中搜索,甚至是 oracle documentation但我还是不明白。 没有类似的例子:(

问题是如何做一个栈和队列模拟器。

  1. 生成 10 个随机数。完成。
  2. 在表格中显示数字。完成。
  3. 使用这 10 个随机数来模拟堆栈和队列。我现在不知道如何将服务与 TextField 通信。
  4. 暂停模拟。或停止。

-程序需要暂停方法。我不知道如何暂停 thead。也许用 wait() 和 notify()。我不知道。

我使用了 label.textProperty.bind(service.progressProperty())。这有效但是当我尝试绑定(bind)一个变量时 updateProgress(i,n) 方法抛出异常。

也许我需要使用 2 个任务。

主类:

package simulation;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

@Override
public void start(Stage primaryStage) throws Exception{
    Parent root = FXMLLoader.load(getClass().getResource("/simulation/simulation.fxml"));
    primaryStage.setTitle("JavaFX and concurrency, Stack and Queue");
    primaryStage.setScene(new Scene(root));
    primaryStage.show();
}


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

Controller 类:

package simulation;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;

import java.net.URL;
import java.util.ResourceBundle;

public class SimulationCt implements Initializable {

  @FXML private TableView table;
  @FXML private TableColumn i_tc;
  @FXML private TableColumn random_tc;

  @FXML private TextField stack_start;
  @FXML private TextField stack_1;
  @FXML private TextField stack_2;
  @FXML private TextField stack_3;
  @FXML private TextField stack_4;
  @FXML private TextField stack_5;
  @FXML private TextField stack_final;

  @FXML private TextField queue_start;
  @FXML private TextField queue_1;
  @FXML private TextField queue_2;
  @FXML private TextField queue_3;
  @FXML private TextField queue_4;
  @FXML private TextField queue_5;
  @FXML private TextField queue_final;

  @FXML private Button new_b;
  @FXML private Button play_pause_b;
  @FXML private Button stop_b;

  @FXML private ProgressBar progress_bar;

  private ObservableList<RandomNumber> numberList = FXCollections.observableArrayList();

  private CalculateService backProcess;

  @FXML
  private void createNew () {
    disableNew(true);
    generateRandom();
    backProcess = new CalculateService();
    progress_bar.progressProperty().bind(backProcess.progressProperty());
    Platform.runLater(() -> {
      backProcess.start();
    });
  }

  @FXML
  private void playPause () {
    if(backProcess.getState().equals(Worker.State.RUNNING)) {
      System.out.println("stoping...");
      backProcess.cancel();
    } else if (backProcess.getState().equals(Worker.State.CANCELLED)) {
      System.out.println("restarting...");
      backProcess.restart();
    }
  }

  @FXML
  private void stop () {
    if(backProcess.getState().equals(Worker.State.RUNNING)) {
      System.out.println("stoping...");
      backProcess.cancel();
    } else if (backProcess.getState().equals(Worker.State.CANCELLED)) {
      System.out.println("already stoped...");
    }
    clearItems();
    disableNew(false);
  }

  // cleans the list and the progress bar.
  private void clearItems () {
    progress_bar.progressProperty().unbind();
    progress_bar.progressProperty().set(0.0);
    numberList.clear();
  }

  private void disableNew (boolean b) {
    new_b.setDisable(b);
    play_pause_b.setDisable(!b);
    stop_b.setDisable(!b);
  }

  // generates random numbers to fill the table, these numbers are the ones for the stack and the queue.
  private void generateRandom () {
    for (int i = 1; i < 11; i++) {
      int rnd = (int)(Math.random() * (200 - 0 + 1)) + 0;
      numberList.add(new RandomNumber(i, rnd ));
    }
  }

  private void startTable () {
    i_tc.setCellValueFactory( new PropertyValueFactory("i"));
    random_tc.setCellValueFactory( new PropertyValueFactory("number"));
    table.setItems(numberList);
  }

  @Override
  public void initialize(URL url, ResourceBundle rb) {
    disableNew(false);
    startTable();
  }
}

FXML:

  <?xml version="1.0" encoding="UTF-8"?>

  <?import javafx.scene.control.Button?>
  <?import javafx.scene.control.Label?>
  <?import javafx.scene.control.ProgressBar?>
  <?import javafx.scene.control.TableColumn?>
  <?import javafx.scene.control.TableView?>
  <?import javafx.scene.control.TextField?>
  <?import javafx.scene.layout.AnchorPane?>
  <?import javafx.scene.layout.ColumnConstraints?>
  <?import javafx.scene.layout.GridPane?>
  <?import javafx.scene.layout.HBox?>
  <?import javafx.scene.layout.RowConstraints?>
  <?import javafx.scene.layout.VBox?>

  <AnchorPane id="AnchorPane" prefHeight="300.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="simulation.SimulationCt">
  <children>
        <TableView fx:id="table" layoutX="8.0" layoutY="10.0" prefHeight="282.0" prefWidth="162.0">
          <columns>
            <TableColumn fx:id="i_tc" prefWidth="28.0" text="i" />
            <TableColumn fx:id="random_tc" prefWidth="122.0" text="Random Number" />
          </columns>
        </TableView>
        <GridPane layoutX="303.0">
           <columnConstraints>
              <ColumnConstraints fillWidth="false" minWidth="10.0" prefWidth="50.0" />
              <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
              <ColumnConstraints fillWidth="false" halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="50.0" />
           </columnConstraints>
           <rowConstraints>
              <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
              <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
           </rowConstraints>
           <children>
              <Label text="Stack" />
              <VBox GridPane.rowIndex="1" GridPane.valignment="TOP">
                 <children>
                    <TextField fx:id="stack_start" prefHeight="25.0" prefWidth="25.0" />
                    <Label text="new" />
                 </children>
              </VBox>
              <VBox GridPane.columnIndex="1" GridPane.rowIndex="1">
                 <children>
                    <TextField fx:id="stack_1" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="stack_2" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="stack_3" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="stack_4" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="stack_5" prefHeight="25.0" prefWidth="25.0" />
                 </children>
              </VBox>
              <VBox GridPane.columnIndex="2" GridPane.rowIndex="1" GridPane.valignment="TOP">
                 <children>
                    <TextField fx:id="stack_final" prefHeight="25.0" prefWidth="25.0" />
                    <Label text="last" />
                 </children>
              </VBox>
           </children>
        </GridPane>
        <GridPane layoutX="193.0" layoutY="155.0">
           <columnConstraints>
              <ColumnConstraints fillWidth="false" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
              <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
              <ColumnConstraints fillWidth="false" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
           </columnConstraints>
           <rowConstraints>
              <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
              <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
           </rowConstraints>
           <children>
              <Label text="Queue" GridPane.columnIndex="1" />
              <VBox GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="TOP">
                 <children>
                    <TextField fx:id="queue_start" prefHeight="25.0" prefWidth="25.0" />
                    <Label text="new" />
                 </children>
              </VBox>
              <HBox spacing="5.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
                 <children>
                    <TextField fx:id="queue_1" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="queue_2" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="queue_3" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="queue_4" prefHeight="25.0" prefWidth="25.0" />
                    <TextField fx:id="queue_5" prefHeight="25.0" prefWidth="25.0" />
                 </children>
              </HBox>
              <VBox GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="TOP">
                 <children>
                    <TextField fx:id="queue_final" prefHeight="25.0" prefWidth="25.0" />
                    <Label text="last" />
                 </children>
              </VBox>
           </children>
        </GridPane>
        <Button fx:id="new_b" onAction="#createNew" layoutX="266.0" layoutY="243.0" mnemonicParsing="false" text="New" />
        <Button fx:id="play_pause_b" onAction="#playPause" layoutX="326.0" layoutY="243.0" mnemonicParsing="false" text="Play / Pause" />
        <Button fx:id="stop_b" onAction="#stop" layoutX="428.0" layoutY="243.0" mnemonicParsing="false" text="Stop" />
        <ProgressBar fx:id="progress_bar" layoutX="266.0" layoutY="277.0" prefWidth="200.0" progress="0.0" />
  </children>
  </AnchorPane>

数据助手:

package simulation;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

public class RandomNumber {

  private IntegerProperty i;
  private IntegerProperty number;

  public RandomNumber(int i, int number) {
    this.i = new SimpleIntegerProperty(i);
    this.number = new SimpleIntegerProperty(number);
  }

  public int getI() {
    return i.get();
  }

  public IntegerProperty iProperty() {
    return i;
  }

  public void setI(int i) {
    this.i.set(i);
  }

  public int getNumber() {
    return number.get();
  }

  public IntegerProperty numberProperty() {
    return number;
  }

  public void setNumber(int number) {
    this.number.set(number);
  }

}

服务类:

package simulation;

import javafx.concurrent.Service;
import javafx.concurrent.Task;

public class CalculateService extends Service {
  int n = 20; // this does the trick to simulate the pause.
  int j = 0; // even if the task is canceled the last value is saved here.

  @Override
  protected Task createTask() {
    return new Task() {
      @Override protected Void call() throws Exception {
        int a;
        int b;
        int iterations;
        for (iterations = j; iterations <= n; iterations++) {
          j = iterations;
          if (isCancelled()) {
            updateMessage("Cancelled");
            break;
          }
          updateProgress(iterations, n);
          System.out.println("number: " + j);

          //Block the thread for a short time, but be sure
          //to check the InterruptedException for cancellation
          try {
            Thread.sleep(100);
          } catch (InterruptedException interrupted) {
            if (isCancelled()) {
              updateMessage("Cancelled");
              break;
            }
          }
        }
        return null;
      }
    };
  }
}

enter image description here

最佳答案

问题中有很多代码,我认为您不需要所有代码来解决您实际询问的概念。所以我在这里只给出一个高层次的答案。如果您想将问题编辑为更简单的内容来解决实际问题,那么我可以针对该示例进行具体说明。

我可能会尝试完全不使用线程来执行此操作,而是使用动画 API。例如,您可以使用具有以下基本轮廓的 Timeline:

public class Controller {

    // @FXML-annotated UI elements...
    // Other state....

    private Timeline timeline ;

    @FXML
    public void initialize() {

        timeline = new Timeline(new KeyFrame(Duration.seconds(100)), e -> {
            if (moreStepsToDo()) {
                doNextStep();
            } else {
                stopSimulation();
            }
        });
        timeline.setCycleCount(Animation.INDEFINITE);
    }

    private boolean moreStepsToDo() {
        // return true if there are more steps in the simulation,
        // false otherwise
    }

    private void doNextStep() {
        // do next step in the simulation
    }

    @FXML
    private void stopSimulation() {
        timeline.stop();
    }

    @FXML
    private void pauseSimulation() {
        timeline.pause();
    }

    @FXML
    private void playSimulation() {
        timeline.play();
    }

    @FXML
    private void resetSimulation() {
        timeline.jumpTo(Duration.ZERO);
    }
}

此解决方案的优点在于一切都是单线程的:关键帧的事件处理程序在 FX 应用程序线程上执行,该线程与执行事件处理程序的线程相同。这意味着无需担心跨线程同步数据。动画 API 中的预定义方法 pause()play()stop() 提供了您正在寻找的功能;您只需适本地更新应用程序状态即可。

这是一个使用这种方法的简单完整示例(它只是移动一堆矩形,一次一个,从一个 vbox 到另一个 vbox)。

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class SimplePausableAnimation extends Application {

    private VBox left;
    private VBox right;
    private Timeline timeline;
    private Button pausePlay;

    @Override
    public void start(Stage primaryStage) {
        left = new VBox(10);
        left.setMinWidth(200);
        right = new VBox(10);
        right.setMinWidth(200);

        HBox hbox = new HBox(10, left, right);

        pausePlay = new Button();

        Button reset = new Button("Reset");
        reset.setOnAction(e -> reset());

        reset();

        BorderPane root = new BorderPane(hbox);

        HBox buttons = new HBox(5, pausePlay, reset);
        buttons.setAlignment(Pos.CENTER);

        root.setBottom(buttons);

        Scene scene = new Scene(root, 600, 600);

        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private void reset() {

        if (timeline != null) {
            timeline.stop();
        }

        left.getChildren().clear();
        right.getChildren().clear();

        for (int i = 0; i < 5; i++) {
            left.getChildren().add(new Rectangle(100, 100, Color.CORNFLOWERBLUE));
        }

        timeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> {
            if (moreStepsToDo()) {
                doNextStep();
            } else {
                timeline.stop();
            }
        }));
        timeline.setCycleCount(Animation.INDEFINITE);

        pausePlay.disableProperty().bind(Bindings.createBooleanBinding(() -> {
            if (left.getChildren().isEmpty()) {
                return true;
            }
            return false;
        }, left.getChildren()));

        pausePlay.textProperty().bind(Bindings.createStringBinding(() -> {
            if (timeline.getStatus() == Animation.Status.RUNNING) {
                return "Pause";
            }
            return "Play";
        }, timeline.statusProperty()));

        pausePlay.setOnAction(e -> {
            if (timeline.getStatus() == Animation.Status.RUNNING) {
                timeline.pause();
            } else {
                timeline.play();
            }
        });
    }

    private boolean moreStepsToDo() {
        return !left.getChildren().isEmpty();
    }

    private void doNextStep() {
        int n = left.getChildren().size();
        Node node = left.getChildren().remove(n - 1);
        right.getChildren().add(node);
    }

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

如果您确实想对线程执行此操作,暂停线程的一种方法是使用 Semaphore有一个许可证。这通常看起来像这样:

Semaphore pauser = new Semaphore(1);

Thread simulationThread = new Thread(() -> {
    try {
        while (! Thread.currentThread().isInterrupted()) {
            pauser.acquire();
            // do simulation step
            pauser.release();
            Thread.sleep(100);
        }
    } catch (InterruptedException exc) {
        // ignore and exit thread...
    }
});

(显然,同样的用法也适用于您的 Task,它在后台线程上执行。)

然后从 Controller 调用pauser.acquire();会暂停模拟(因为模拟线程将无法获取许可),并调用pauser.release() 暂停时将让它再次运行。

关于java - 如何使用暂停、线程(任务、服务)模拟队列和堆栈 JavaFX,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49390022/

相关文章:

javascript - nodejs 是否支持多线程?

没有 JRE 的 JavaFX DEB 包不起作用

单击按钮文本时 JavaFX 按钮出现错误

java - 同步的奇怪行为

java - CDATA XML 在解析时被截断

Java:纪元日期为 MM/DD/YYYY

java - 如何判断提交的表单是否有多个同名元素

c - Linux 上的 RPC 多线程服务器 - Pthreads

java - 如何更改CSS上的字体?

java - authc 过滤器未使用 spring 在 shiro 中调用 MyRealm