我如何获得多个Java线程以应用户的请求暂停和恢复?

问题描述 投票:0回答:1

我正在创建一个20分钟的倒数计时器应用程序。我正在使用JavaFX SceneBuilder来做到这一点。计时器由两个标签(一个为分钟,一个为秒,每个标签由CountdownTimer类对象组成)和进度条组成(计时器看起来像this)。这些组件中的每一个都是独立的,并且同时在独立的线程上运行,以防止UI冻结。而且有效。

问题:

我需要能够暂停和恢复的三个线程(minutesThreadsecondsThreadprogressBarUpdaterThread)是常规的.java类。当用户单击播放(开始)按钮时,单击将向FXMLDocumentController(控制UI中组件更新方式的类)方法startTimer()发信号,以进行有关计时器的工作。

现在startTimer()中唯一的功能FXMLDocumentController是:用户单击播放(开始)按钮->计时器开始倒计时。

我希望用户能够使用相同的按钮暂停和恢复计时器。我已经尝试过在FXMLDocumentController类和其他三个线程之间使用同步,但没有以多种不同的方式使用(诚然,我几乎没有并发编码的经验)。我只想能够暂停播放计时器!

任何人都可以向我提供有关如何进行此操作的建议吗?预先感谢。

FXMLDocumentController.java中的startTimer()(用于启动倒数计时器):

@FXML
void startTimer(MouseEvent event) throws FileNotFoundException {
    // update click count so user can switch between pause and start
    startTimerButtonClickCount++;

    // create a pause button image to replace the start button image when the user pauses the timer
    Image pauseTimerButtonImage = new Image(new 
         FileInputStream("/Users/Home/NetBeansProjects/Take20/src/Images/pause2_black_18dp.png"));
    // setting imageview to be used when user clicks on start button to pause it
    ImageView pauseTimerButtonImageView = new ImageView(pauseTimerButtonImage);
    // setting the width and height of the pause image
    pauseTimerButtonImageView.setFitHeight(31);
    pauseTimerButtonImageView.setFitWidth(28);
    // preserving the pause image ratio after resize
    pauseTimerButtonImageView.setPreserveRatio(true);

    // create a start button image to replace the pause button image when the user unpauses the timer
    Image startTimerButtonImage = new Image(new 
          FileInputStream("/Users/Home/NetBeansProjects/
          Take20/src/Images/play_arrow2_black_18dp.png"));
    ImageView startTimerButtonImageView = new ImageView(startTimerButtonImage);
    startTimerButtonImageView.setFitHeight(31);
    startTimerButtonImageView.setFitWidth(28);
    startTimerButtonImageView.setPreserveRatio(true);

    // progressBar updater
    ProgressBarUpdater progressBarUpdater = new ProgressBarUpdater();
    TimerThread progressBarThread = new TimerThread(progressBarUpdater);
    // minutes timer
    CountdownTimer minutesTimer = new CountdownTimer(19);
    TimerThread minutesThread = new TimerThread(minutesTimer);
    // seconds timer
    CountdownTimer secondsTimer = new CountdownTimer(59);
    TimerThread secondsThread = new TimerThread(secondsTimer);

    // bind our components in order to update them
    progressBar.progressProperty().bind(progressBarUpdater.progressProperty());
    minutesTimerLabel.textProperty().bind(minutesTimer.messageProperty());
    secondsTimerLabel.textProperty().bind(secondsTimer.messageProperty());

    // start the threads in order to have them run parallel when the start button is clicked
    progressBarThread.start();
    minutesThread.start();
    secondsThread.start();

    // if the start button was clicked, then we set its graphic to the pause image
    // if the button click count is divisible by 2, we pause it, otherwise, we play it (and change 
    // the button images accordingly).
    if (startTimerButtonClickCount % 2 == 0) {
        startTimerButton.setGraphic(pauseTimerButtonImageView);
        progressBarThread.pauseThread();
        minutesThread.pauseThread();
        secondsThread.pauseThread();

        progressBarThread.run();
        minutesThread.run();
        secondsThread.run();
    } else {
        startTimerButton.setGraphic(startTimerButtonImageView);
        progressBarThread.resumeThread();
        minutesThread.resumeThread();
        secondsThread.resumeThread();

        progressBarThread.run();
        minutesThread.run();
        secondsThread.run();
    }
}

TimerThread(用于在用户单击UI中的播放/暂停按钮时挂起/恢复计时器线程:] >>

public class TimerThread extends Thread implements Runnable {

public boolean paused = false;
public final Task<Integer> timerObject;
public final Thread thread;

public TimerThread(Task timerObject) {
    this.timerObject = timerObject;
    this.thread = new Thread(timerObject);
}

@Override
public void start() {
    this.thread.start();
    System.out.println("TimerThread started");
}

@Override
public void run() {
    System.out.println("TimerThread class run() called");
    try {
        synchronized (this.thread) {
            System.out.println("synchronized called");
            while (paused) {
                System.out.println("wait called");
                this.thread.wait();
                System.out.println("waiting...");
            }
        }
    } catch (Exception e) {
        System.out.println("exception caught in TimerThread");
    }

}

synchronized void pauseThread() {
    paused = true;
}

synchronized void resumeThread() {
    paused = false;
    notify();
}
}

CountdownTimer.java(用于创建和更新倒数计时器的分钟和秒):

public class CountdownTimer extends Task<Integer> {

private int time;
private Timer timer;
private int timerDelay;
private int timerPeriod;
private int repetitions;

public CountdownTimer(int time) {
    this.time = time;
    this.timer = new Timer();
    this.repetitions = 1;
}

@Override
protected Integer call() throws Exception {
    // we will create a new thread for each time unit (minutes, seconds)
    // we start with whatever time is passed to the constructor
    // we have threads devoted to each case so both minutes and second cases can run parallel to each other.
    switch (time) {
        // for our minutes timer
        case 19:
            // first display should be 19 first since our starting timer time should be 19:59 
            updateMessage("19");

            // set delay and period to change every minute of the countdown
            // 60,000 milliseconds in one minute
            timerDelay = 60000;
            timerPeriod = 60000;
            System.out.println("Running minutesthread....");

            // use a timertask to loop through time at a fixed rate as set by timerDelay, until the timer reaches 0 and is cancelled
            timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    //check if the flag is divisible by 2, then we sleep this thread 

                    // if time reaches 0, we want to update the minute label to 00
                    if (time == 0) {
                        updateMessage("0" + Integer.toString(time));
                        timer.cancel();
                        timer.purge();
                        // if the time is a single digit, append a 0 and reduce time by 1
                    } else if (time <= 10) {
                        --time;
                        updateMessage("0" + Integer.toString(time));
                        // otherwise, we we default to reducing time by 1, every minute
                    } else {
                        --time;
                        updateMessage(Integer.toString(time));
                    }
                }
            }, timerDelay, timerPeriod);
            // exit switch statement once we finish our work 
            break;

        // for our seconds timer
        case 59:
            // first display 59 first since our starting timer time should be 19:59 
            updateMessage("59");
            // use a counter to count repetitions so we can cancel the timer when it arrives at 0, after 20 repetitions

            // set delay and period to change every second of the countdown
            // 1000 milliseconds in one second
            timerDelay = 1000;
            timerPeriod = 1000;
            System.out.println("Running seconds thread....");

            // use a timertask to loop through time at a fixed rate as set by timerDelay, until the timer reaches 0 and is cancelled
            timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    --time;
                    System.out.println("repititions: " + repetitions);
                    // Use a counter to count repetitions so we can cancel the timer when it arrives at 0, after 1200 repetitions
                    // We will reach 1200 repetitions at the same time as the time variable reaches 0, since the timer 
                    // loops/counts down every second (1000ms).
                    // 1200 seconds = 20 minutes * 60 seconds (1 minute)
                    repetitions++;

                    if (time == 0) {
                        if (repetitions == 1200) {
                            // reset repetitions if user decides to click play again
                            repetitions = 0;
                            timer.cancel();
                            System.out.println("repetitions ran");
                        }
                        updateMessage("0" + Integer.toString(time));
                        // reset timer to 60, so it will countdown again from 60 after reaching 0 (since we have to repeat the seconds timer multiple times,
                        // unlike the minutes timer, which only needs to run once
                        time = 60;
                        System.out.println("time == 00 ran");
                    } else if (time < 10 && time > 0) {
                        updateMessage("0" + Integer.toString(time));
                    } else {
                        updateMessage(Integer.toString(time));
                    }
                }
            }, timerDelay, timerPeriod);
            // exit switch statement once we finish our work
            break;
    }

    return null;
}
}

ProgressBarUpdater.java(用于在倒数计时器倒计时时更新进度条):

public class ProgressBarUpdater extends Task<Integer> {

private int progressBarPeriod;
private Timer timer;
private double time;

public ProgressBarUpdater() {
    this.timer = new Timer();
    this.time = 1200000;
}

@Override
protected Integer call() throws Exception {
    progressBarPeriod = 10;
    System.out.println("Running progressBar thread....");

    // using a timer task, we update our progressBar by reducing the filled progressBar every 9.68 milliseconds 
    // (instead of 10s to account for any delay in program runtime) to ensure that the progressBar ends at the same time our timer reaches 0. 
    // according to its max (1200000ms or 20 minutes)
    timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            time -= 9.68;
            updateProgress(time, 1200000);
            System.out.println("progressBarUpdater is running");
        }
    }, 0, progressBarPeriod);

    return null;
}

@Override
protected void updateProgress(double workDone, double maxTime) {
    super.updateProgress(workDone, maxTime);
}

}

我正在创建一个20分钟的倒数计时器应用程序。我正在使用JavaFX SceneBuilder来做到这一点。计时器由两个标签组成(一个为分钟,一个为秒,每个都由CountdownTimer组成...

java multithreading javafx synchronization scenebuilder
1个回答
0
投票

正如我在评论中提到的那样,为此使用后台线程,再加上三个

© www.soinside.com 2019 - 2024. All rights reserved.