这是一个简单的秒表。看起来很简单,但是这里有一个设计上的问题。一般初学者会创建一个循环的线程讲一个整数叠加,该线程隔一段时间暂停一下,比如暂停 10 毫秒,然后往这个整数上加 10。
这样设计的问题在于,线程的暂停和继续,以及计数和显示都是要花费时间的。所以这样的程序运行越久,误差就会越大。下面这个例子就是经过改良的,能够将时间误差维持在极低的水平上。
- import javax.swing.*;
- import java.awt.HeadlessException;
- import java.awt.BorderLayout;
- import java.awt.FlowLayout;
- import java.awt.Font;
- import java.awt.event.ActionListener;
- import java.awt.event.ActionEvent;
- /**
- * 小小的计时器
- */
- public class TimerFrame extends JFrame {
- private static final String INITIAL_LABEL_TEXT = "00:00:00 000";
- // 计数线程
- private CountingThread thread = new CountingThread();
- // 记录程序开始时间
- private long programStart = System.currentTimeMillis();
- // 程序一开始就是暂停的
- private long pauseStart = programStart;
- // 程序暂停的总时间
- private long pauseCount = 0;
- private JLabel label = new JLabel(INITIAL_LABEL_TEXT);
- private JButton startPauseButton = new JButton("开始");
- private JButton resetButton = new JButton("清零");
- private ActionListener startPauseButtonListener = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (thread.stopped) {
- pauseCount += (System.currentTimeMillis() - pauseStart);
- thread.stopped = false;
- startPauseButton.setText("暂停");
- } else {
- pauseStart = System.currentTimeMillis();
- thread.stopped = true;
- startPauseButton.setText("继续");
- }
- }
- };
- private ActionListener resetButtonListener = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- pauseStart = programStart;
- pauseCount = 0;
- thread.stopped = true;
- label.setText(INITIAL_LABEL_TEXT);
- startPauseButton.setText("开始");
- }
- };
- public TimerFrame(String title) throws HeadlessException {
- super(title);
- setDefaultCloseOperation(EXIT_ON_CLOSE);
- setLocation(200, 200);
- setResizable(false);
- setupBorder();
- setupLabel();
- setupButtonsPanel();
- startPauseButton.addActionListener(startPauseButtonListener);
- resetButton.addActionListener(resetButtonListener);
- thread.start(); // 计数线程一直就运行着
- }
- // 为窗体面板添加边框
- private void setupBorder() {
- JPanel contentPane = new JPanel(new BorderLayout());
- contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
- this.setContentPane(contentPane);
- }
- // 配置按钮
- private void setupButtonsPanel() {
- JPanel panel = new JPanel(new FlowLayout());
- panel.add(startPauseButton);
- panel.add(resetButton);
- add(panel, BorderLayout.SOUTH);
- }
- // 配置标签
- private void setupLabel() {
- label.setHorizontalAlignment(SwingConstants.CENTER);
- label.setFont(new Font(label.getFont().getName(), label.getFont().getStyle(), 40));
- this.add(label, BorderLayout.CENTER);
- }
- // 程序入口
- public static void main(String[] args) {
- try {
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
- } catch (Exception e) {
- e.printStackTrace();
- }
- TimerFrame frame = new TimerFrame("小小计时器");
- frame.pack();
- frame.setVisible(true);
- }
- private class CountingThread extends Thread {
- public boolean stopped = true;
- private CountingThread() {
- setDaemon(true);
- }
- @Override
- public void run() {
- while (true) {
- if (!stopped) {
- long elapsed = System.currentTimeMillis() - programStart - pauseCount;
- label.setText(format(elapsed));
- }
- try {
- sleep(17); // 使时钟显得更乱
- } catch (InterruptedException e) {
- e.printStackTrace();
- System.exit(1);
- }
- }
- }
- // 将毫秒数格式化
- private String format(long elapsed) {
- int hour, minute, second, milli;
- milli = (int) (elapsed % 1000);
- elapsed = elapsed / 1000;
- second = (int) (elapsed % 60);
- elapsed = elapsed / 60;
- minute = (int) (elapsed % 60);
- elapsed = elapsed / 60;
- hour = (int) (elapsed % 60);
- return String.format("%02d:%02d:%02d %03d", hour, minute, second, milli);
- }
- }
- }