Trying to stop a single thread out of multiple running at the same time in java

2020-02-14 java multithreading javafx textfield java-threads

So im trying to stop a single thread when I have multiple threads running, here is the code im using to initialise the threads. Basically I have multiple textFields in javafx, and when a button is clicked on the screen, it fills the textFields, one by one, with an incrementing timer. Now I also have a button for each of the textfields to clear it, but the problem is when I clear it, because the thread is still running, the timer vanishes for a second and comes back because of the line 'orderTimes.get(boxNo).setText(minute + second);' in the code.

Now what I've tried is creating a list of threads and I've tried implementing it below but it doesn't work, this is so I can call each individual thread if its button to clear has been clicked.

Does anyone know how I can close/stop only one single thread out of multiple that are running? If more info is needed just let me know, thanks.

public static void createIncrementingTimer(int boxNo, List<TextField> orderTimes) {
minutesList.set(boxNo, 0);
secondsList.set(boxNo, 0);
state = true;
new Thread(threadList.get(boxNo))  {
  int currentMinutes = 0;
  int currentSeconds = 0;
  public void run() {
    for (;;) {
      if (state = true) {
        try {
          sleep(1000);
          if (secondsList.get(boxNo) > 59) {
            secondsList.set(boxNo, 0);
            currentSeconds = 0;
            minutesList.set(boxNo, currentMinutes + 1);
            currentMinutes++;
          }            
          if (secondsList.get(boxNo) < 10) {
            second = ":0" + Integer.toString(secondsList.get(boxNo));
          } else {
            second = ":" + Integer.toString(secondsList.get(boxNo));                 
          }                            
          secondsList.set(boxNo, currentSeconds + 1);
          currentSeconds++;            
          if (minutesList.get(boxNo) < 10) {
            minute = "0" + Integer.toString(minutesList.get(boxNo));
          } else {
            minute = Integer.toString(minutesList.get(boxNo));
          }
          orderTimes.get(boxNo).setText(minute + second);             
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }
};
threadList.get(boxNo).start();
}

The code I'm using to clear the textfields is below, with orderTimes being the list of textFields that I'm trying to clear.

public static void eraseBox(int clickedButtonNumber, List<TextArea> orderContentsList, List<TextField> tableNumbers, List<TextField> orderNumbers, List<TextField> orderTimes) {
orderContentsList.get(clickedButtonNumber).setText(null);
  tableNumbers.get(clickedButtonNumber).clear();
  orderNumbers.get(clickedButtonNumber).clear();
  orderTimes.get(clickedButtonNumber).clear();
}

Answers

I would suggest you try to avoid Threads. The Animation API is designed to make doing work that would normally be done in a Thread easier. In this example, the IncrementingTimer class consists of two Labels and three Buttons. The Labels are used to show the time. The Buttons are used to control the Timeline. The Timeline is used to increment the Labels value each second or every sixty seconds. I have added three IncrementingTimers to the app.

Main

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * JavaFX App
 */
public class App extends Application {

    @Override
    public void start(Stage stage) {        
        var scene = new Scene(new VBox(new IncrementingTimer(), new IncrementingTimer(), new IncrementingTimer()), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

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

IncrementingTimer

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.util.Duration;

/**
 *
 * @author blj0011
 */
final public class IncrementingTimer extends HBox
{
    IntegerProperty secondsCounter = new SimpleIntegerProperty();//Keeps up with seconds
    IntegerProperty minutesCounter = new SimpleIntegerProperty();//Keeps up with minutes

    Label lblSeconds = new Label();//Displays the seconds
    Label lblMinutes = new Label();//Displays the minutes
    Label lblColon = new Label(":");//Display the colon between minutes and seconds

    Button btnPlay = new Button("Play");//Plays the Timeline
    Button btnStop = new Button("Stop");//Stops the Timeline
    Button btnPause = new Button("Pause");//Pauses the Timeline

    Timeline timeline;//Used to run code that changes the Labels. This Timeline runs every one second.

    public IncrementingTimer()
    {
        lblSeconds.textProperty().bind(secondsCounter.asString("%02d"));//Binds the seconds label to the seconds counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.
        lblMinutes.textProperty().bind(minutesCounter.asString("%02d"));//Binds the minutes label to the minutes counter. Sets the String to always show two digits. Exmaple 1 is shown as 01.

        getChildren().addAll(lblMinutes, lblColon, lblSeconds, btnPlay, btnStop, btnPause);

        timeline = new Timeline(new KeyFrame(Duration.seconds(1), (event) -> {//Replace the one with .016 to speed this up for testing purposes.
            secondsCounter.set(secondsCounter.get() + 1);
            if (secondsCounter.get() == 60) {
                secondsCounter.set(0);
                minutesCounter.set(minutesCounter.get() + 1);
                if (minutesCounter.get() == 60) {
                    minutesCounter.set(0);
                }
            }
        }));
        timeline.setCycleCount(Timeline.INDEFINITE);
        btnPlay.setOnAction((event) -> {
            timeline.play();
        });
        btnPause.setOnAction((event) -> {
            timeline.pause();
        });
        btnStop.setOnAction((event) -> {
            timeline.stop();
            secondsCounter.set(0);
            minutesCounter.set(0);
        });

        this.setAlignment(Pos.CENTER);
    }

}

The if(state=true) should rather be if(state==true) or just if(state), but in fact the for(;;) could do the entire thing as while(state), simply shutting down the thread when you set state=false.

Then, fully stopping the thread could happen as state=false;threadList.get(boxNo).join();, and you can clear the field only after that (since the thread will set it to something in the last step too).


With a simpler approach you could throw away the state, and revert to for(;;), with the twist of having the try-catch() around the loop, outside. This way you can use threadList.get(boxNo).interrupt();threadList.get(boxNo);.join(); to stop the thread, and on top of that it will be immediate, as the sleep() ends immediately when the thread is interrupted.

As recommended and demonstrated by Sedric, use JavaFx Animation tools for the counters.
The following one-file mre demonstrating implementation of counters using two different animation tools.
One uses PauseTransition and uses Timeline, each with its stop button.
(copy-paste the entire code into Timers.java and run)

import java.io.IOException;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Timers extends Application {

    @Override public void start(final Stage stage) throws IOException {
        VBox root = new VBox(new CounterPane(new TimeLineCounter()), new CounterPane(new PauseTransitionCounter()));
        stage.setScene(new Scene(root));
        stage.show();
    }

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

class CounterPane extends HBox{

    private final Counter counter;
    CounterPane(Counter counter) {
        super(5);
        this.counter = counter; //todo: check not null
        Button stopBtn = new Button("Stop");
        stopBtn.setOnAction(e->stop());
        getChildren().addAll(stopBtn, counter);
    }

    void stop(){
        counter.getAnimation().stop();
    }
}

abstract class Counter extends Label {

    protected int count = 0;
    public Counter() {
        setAlignment(Pos.CENTER); setPrefSize(25, 25);
        count();
    }

    abstract void count();
    abstract Animation getAnimation();
}

class TimeLineCounter extends Counter {

    private Timeline timeline;

    @Override
    void count() {

        timeline = new Timeline();
        timeline.setCycleCount(Animation.INDEFINITE);
        final KeyFrame keyFrame = new KeyFrame(
                Duration.seconds(1),
                event -> {  setText(String.valueOf(count++) );  }
                );
        timeline.getKeyFrames().add(keyFrame);
        timeline.play();
    }

    @Override
    Animation getAnimation() {
        return timeline;
    }
}

class PauseTransitionCounter extends Counter {

    private PauseTransition pauseTransition;

    @Override
    void count() {

        pauseTransition = new PauseTransition(Duration.seconds(1));
        pauseTransition.setOnFinished(event ->{
            setText(String.valueOf(count++) );
            pauseTransition.play();
        });
        pauseTransition.play();
    }

    @Override
    Animation getAnimation() {
        return pauseTransition;
    }
}

Related