gpt4 book ai didi

java - 如何根据用户的请求暂停和恢复多个 Java 线程?

转载 作者:行者123 更新时间:2023-12-02 08:42:08 25 4
gpt4 key购买 nike

我正在创建一个 20 分钟倒计时器应用程序。我正在使用 JavaFX SceneBuilder 来执行此操作。该计时器由两个标签(一个表示分钟,一个表示秒——每个标签由一个 CountdownTimer 类对象组成)和一个进度条(计时器看起来像 this )组成。这些组件中的每一个都是独立的,并且同时在单独的线程上运行,以防止 UI 卡住。并且它有效。

问题:

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

目前,FXMLDocumentControllerstartTimer() 的唯一功能是:用户单击播放(开始)按钮 --> 计时器开始倒计时。

我希望用户能够使用同一个按钮暂停和恢复计时器。我尝试过在 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);
}

}

最佳答案

正如我在评论中提到的,为此使用后台线程,更不用说三个(!) 后台线程,只会使实现和推理变得更加困难。最好使用animation API由 JavaFX 提供 - 它是异步的,但仍然在 JavaFX 应用程序线程上执行。正如其他人所提到的,您只需要一个值来表示剩余时间,另一个值表示持续时间。从那里您可以显示分钟、秒和进度。

就我个人而言,我会使用 AnimationTimer因为它为您提供当前帧的时间戳,您可以使用它来计算剩余时间。为了使事情更容易使用,我还将 AnimationTimer 包装在另一个类中,并让后一个类公开更适合倒计时器的 API。例如:

package com.example;

import java.util.concurrent.TimeUnit;
import javafx.animation.AnimationTimer;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleLongProperty;

public class CountdownTimer {

private static long toMillis(long nanos) {
return TimeUnit.NANOSECONDS.toMillis(nanos);
}

/* *********************************************************************
* *
* Instance Fields *
* *
***********************************************************************/

private final Timer timer = new Timer();
private long cachedDuration;

/* *********************************************************************
* *
* Constructors *
* *
***********************************************************************/

public CountdownTimer() {}

public CountdownTimer(long duration) {
setDuration(duration);
}

/* *********************************************************************
* *
* Public API *
* *
***********************************************************************/

public void start() {
if (getStatus() == Status.READY || getStatus() == Status.PAUSED) {
timer.start();
setStatus(Status.RUNNING);
}
}

public void pause() {
if (getStatus() == Status.RUNNING) {
timer.pause();
setStatus(Status.PAUSED);
}
}

public void stopAndReset() {
timer.stopAndReset();
setStatus(Status.READY);
}

/* *********************************************************************
* *
* Properties *
* *
***********************************************************************/

private final ReadOnlyObjectWrapper<Status> status = new ReadOnlyObjectWrapper<>(this, "status", Status.READY) {
@Override protected void invalidated() {
if (get() == Status.READY) {
cachedDuration = Math.abs(getDuration());
setTimeRemaining(cachedDuration);
}
}
};
private void setStatus(Status status) { this.status.set(status); }
public final Status getStatus() { return status.get(); }
public final ReadOnlyObjectProperty<Status> statusProperty() { return status.getReadOnlyProperty(); }

private final LongProperty duration = new SimpleLongProperty(this, "duration") {
@Override protected void invalidated() {
if (getStatus() == Status.READY) {
cachedDuration = Math.abs(get());
setTimeRemaining(cachedDuration);
}
}
};
public final void setDuration(long duration) { this.duration.set(duration); }
public final long getDuration() { return duration.get(); }
public final LongProperty durationProperty() { return duration; }

private final ReadOnlyLongWrapper timeRemaining = new ReadOnlyLongWrapper(this, "timeRemaining") {
@Override protected void invalidated() {
setProgress((double) (cachedDuration - get()) / (double) cachedDuration);
}
};
private void setTimeRemaining(long timeRemaining) { this.timeRemaining.set(timeRemaining); }
public final long getTimeRemaining() { return timeRemaining.get(); }
public final ReadOnlyLongProperty timeRemainingProperty() { return timeRemaining.getReadOnlyProperty(); }

private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress");
private void setProgress(double progress) { this.progress.set(progress); }
public final double getProgress() { return progress.get(); }
public final ReadOnlyDoubleProperty progressProperty() { return progress.getReadOnlyProperty(); }

/* *********************************************************************
* *
* Static Classes *
* *
***********************************************************************/

public enum Status {
READY,
RUNNING,
PAUSED,
FINISHED
}

/* *********************************************************************
* *
* Classes *
* *
***********************************************************************/

private class Timer extends AnimationTimer {

private long triggerTime = Long.MIN_VALUE;
private long pauseTime = Long.MIN_VALUE;
private boolean pausing;

@Override
public void handle(long now) {
if (pausing) {
pauseTime = toMillis(now);
pausing = false;
stop();
} else {
if (triggerTime == Long.MIN_VALUE) {
triggerTime = toMillis(now) + cachedDuration;
} else if (pauseTime != Long.MIN_VALUE) {
triggerTime += toMillis(now) - pauseTime;
pauseTime = Long.MIN_VALUE;
}

long timeRemaining = Math.max(0, triggerTime - toMillis(now));
setTimeRemaining(timeRemaining);
if (timeRemaining == 0) {
setStatus(Status.FINISHED);
stop();
}
}
}

@Override
public void start() {
pausing = false;
super.start();
}

void pause() {
if (triggerTime != Long.MIN_VALUE) {
pausing = true;
} else {
stop();
}
}

void stopAndReset() {
stop();
triggerTime = Long.MIN_VALUE;
pauseTime = Long.MIN_VALUE;
pausing = false;
}
}
}

警告:当AnimationTimer运行时,CountdownTimer实例无法被垃圾回收。

此实现将持续时间和剩余时间值解释为毫秒。此外,启动计时器后更改持续时间只有在重置计时器(即调用 stopAndReset())后才会生效。

<小时/>

下面是在基于 FXML 的应用程序中使用上述 CountdownTimer 的示例。请注意,该示例使用不同的按钮来启动、暂停、恢复和重置计时器。这与您在问题中描述的不同,但您应该能够重新设计以满足您的需求。此外,该示例还提供了一种切换是否显示当前秒的毫秒的方法。

应用程序.fxml:

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.CheckBox?>
<?import com.example.CountdownTimer?>
<?import com.example.CountdownTimer.Status?>

<VBox xmlns="http://javafx.com/javafx/14.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.example.Controller" prefHeight="300" prefWidth="500">

<fx:define>
<!-- 90,000ms == 1m 30s -->
<CountdownTimer fx:id="timer" duration="90000"/>
<CountdownTimer.Status fx:id="READY" fx:value="READY"/>
<CountdownTimer.Status fx:id="RUNNING" fx:value="RUNNING"/>
<CountdownTimer.Status fx:id="PAUSED" fx:value="PAUSED"/>
</fx:define>

<ToolBar style="-fx-font: 10pt 'Monospaced';">
<Button text="Start" disable="${timer.status != READY}" focusTraversable="false"
onAction="#handleStartOrResumeTimer"/>
<Button text="Resume" disable="${timer.status != PAUSED}" focusTraversable="false"
onAction="#handleStartOrResumeTimer"/>
<Button text="Pause" disable="${timer.status != RUNNING}" focusTraversable="false" onAction="#handlePauseTimer"/>
<Button text="Reset" disable="${timer.status == READY || timer.status == RUNNING}" focusTraversable="false"
onAction="#handleResetTimer"/>
<Separator/>
<CheckBox fx:id="showMillisBox" text="Show Millis" focusTraversable="false"/>
</ToolBar>

<ProgressBar progress="${timer.progress}" maxWidth="Infinity"/>

<StackPane VBox.vgrow="ALWAYS">
<Label fx:id="timerLabel" style="-fx-font: bold 48pt 'Monospaced';"/>
</StackPane>
</VBox>

Controller .java:

package com.example;

import java.time.Duration;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;

public class Controller {

@FXML private CountdownTimer timer;
@FXML private CheckBox showMillisBox;
@FXML private Label timerLabel;

@FXML
private void initialize() {
timerLabel
.textProperty()
.bind(
Bindings.createStringBinding(
this::formatTimeRemaining,
timer.timeRemainingProperty(),
showMillisBox.selectedProperty()));
timerLabel
.textFillProperty()
.bind(
Bindings.when(timer.statusProperty().isEqualTo(CountdownTimer.Status.FINISHED))
.then(Color.FIREBRICK)
.otherwise(Color.FORESTGREEN));
}

private String formatTimeRemaining() {
Duration d = Duration.ofMillis(timer.getTimeRemaining());
if (showMillisBox.isSelected()) {
return String.format("%02d:%02d:%03d", d.toMinutes(), d.toSecondsPart(), d.toMillisPart());
}
return String.format("%02d:%02d", d.toMinutes(), d.toSecondsPart());
}

@FXML
private void handleStartOrResumeTimer(ActionEvent event) {
event.consume();
timer.start();
}

@FXML
private void handlePauseTimer(ActionEvent event) {
event.consume();
timer.pause();
}

@FXML
private void handleResetTimer(ActionEvent event) {
event.consume();
timer.stopAndReset();
}
}

Main.java:

package com.example;

import java.io.IOException;
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 IOException {
Parent root = FXMLLoader.load(getClass().getResource("/com/example/App.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Countdown Timer Example");
primaryStage.show();
}
}

关于java - 如何根据用户的请求暂停和恢复多个 Java 线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61313461/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com