The biggest problem you would face is being able to get the various Runnable
s to run at a consistent rate. Basically, there's no real way to know when the Executor
will actual execute the task you provide it, as it has it's own over heads.
In this particular case, I would recommend reducing the number of active Thread
s to one, this reduces any additional overheads involved in the creation and execution of other Thread
s and provides you with the best control over getting things to work as close to the time you want as possible.
Instead of using a Thread
, I would instead, use a javax.swing.Timer
, primary because it's simple and is executed within the context of the EDT which makes it safer to update the UI from within, for example
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class StopwatchGUI3 extends JFrame {
private static final long serialVersionUID = 3545053785228009472L;
// GUI Components
private JPanel panel;
private JLabel timeLabel;
private JPanel buttonPanel;
private JButton startButton;
private JButton resetButton;
private JButton stopButton;
// Properties of Program.
private byte centiseconds = 0;
private byte seconds = 30;
private short minutes = 0;
private DecimalFormat timeFormatter;
private Timer timer;
public StopwatchGUI3() {
panel = new JPanel();
panel.setLayout(new BorderLayout());
timeLabel = new JLabel();
timeLabel.setFont(new Font("Consolas", Font.PLAIN, 13));
timeLabel.setHorizontalAlignment(JLabel.CENTER);
panel.add(timeLabel);
buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
startButton = new JButton("Start");
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
timer.start();
}
});
buttonPanel.add(startButton);
resetButton = new JButton("Reset");
resetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
timer.stop();
centiseconds = 0;
seconds = 30;
minutes = 0;
timeLabel.setText(timeFormatter.format(minutes) + ":"
+ timeFormatter.format(seconds) + "."
+ timeFormatter.format(centiseconds));
}
});
buttonPanel.add(resetButton);
stopButton = new JButton("Stop");
stopButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
timer.stop();
}
});
buttonPanel.add(stopButton);
panel.add(buttonPanel, BorderLayout.SOUTH);
timeFormatter = new DecimalFormat("00");
timer = new Timer(10, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (centiseconds > 0) {
centiseconds--;
} else {
if (seconds == 0 && minutes == 0) {
timer.stop();
} else if (seconds > 0) {
seconds--;
centiseconds = 99;
} else if (minutes > 0) {
minutes--;
seconds = 59;
centiseconds = 99;
}
}
timeLabel.setText(timeFormatter.format(minutes) + ":"
+ timeFormatter.format(seconds) + "."
+ timeFormatter.format(centiseconds));
}
});
timeLabel.setText(timeFormatter.format(minutes) + ":"
+ timeFormatter.format(seconds) + "."
+ timeFormatter.format(centiseconds));
add(panel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setTitle("StopwatchGUI.java");
pack();
setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
new StopwatchGUI3();
}
});
}
}
I would also stop "guessing" at the time. There simply is no guarantee that the amount of time passed between "updates" is accurate.
Instead, I would grab the current time when the stop watch is started and on each tick of the Timer
, subtract it from the current time, giving you the amount of time that has passed. You can then use that to determine what the current value of the stop watch should be...
For example...
Adding the following instance fields...
private long startTime;
private long runTime = 30000; // 30 seconds...
Updating the startButton
to include capturing the start time...
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startTime = System.currentTimeMillis();
timer.start();
}
});
And then updating the Timer
as follows...
timer = new Timer(10, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
long now = System.currentTimeMillis();
long dif = now - startTime;
if (dif >= runTime) {
timer.stop();
dif = runTime;
}
dif = runTime - dif;
long minutes = dif / (60 * 1000);
dif = Math.round(dif % (60 * 1000));
long seconds = dif / 1000;
dif = Math.round(dif % 1000);
long centiseconds = dif / 10;
timeLabel.setText(timeFormatter.format(minutes) + ":"
+ timeFormatter.format(seconds) + "."
+ timeFormatter.format(centiseconds));
}
});
Take a look at Concurrency in Swing for more details