Skip to content
Advertisement

Clock thread in Java

I have been studying about Java Thread recently. I created this simple clock that will display how many seconds have passed. It looked like this
frame

Here is the code for the application above (including 2 classes: Main and ClockPanel)

public class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Clock");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(300, 300);
            frame.setLayout(new GridLayout(1, 1));

            ClockPanel clock = new ClockPanel();
            frame.add(clock);

            frame.setVisible(true);
        });
    }
}
public class ClockPanel extends JPanel implements ActionListener {

    private JLabel timeValue;
    private JButton runButton;
    private JButton pauseButton;
    private JButton resetButton;

    private boolean isRunning;
    private int seconds;


    public ClockPanel() {
        timeValue = new JLabel("0");

        setBorder(BorderFactory.createLineBorder(Color.BLACK));
        setAlignmentX(Component.CENTER_ALIGNMENT);
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        initButtons();

        // create panel to display the clocks
        JPanel timePanel = new JPanel();
        timePanel.setLayout(new BoxLayout(timePanel, BoxLayout.X_AXIS));
        timePanel.setAlignmentX(Component.CENTER_ALIGNMENT);
        JLabel timeLabel = new JLabel("Value");
        timePanel.add(timeLabel);
        timePanel.add(Box.createRigidArea(new Dimension(30, 0)));
        timePanel.add(timeValue);
        add(timePanel);

        add(Box.createRigidArea(new Dimension(0, 20)));

        // create panel to display the buttons
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
        buttonPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
        buttonPanel.add(runButton);
        buttonPanel.add(Box.createRigidArea(new Dimension(30, 0)));
        buttonPanel.add(pauseButton);
        buttonPanel.add(Box.createRigidArea(new Dimension(30, 0)));
        buttonPanel.add(resetButton);
        add(buttonPanel);

        // create thread to run the clock
        Thread clockThread = new Thread(() -> {
            seconds = -1;
            while (true) {
//                System.out.println(); // if this line is removed, the clock will not run
                if (isRunning) {
                    try {
                        seconds++;
                        timeValue.setText(String.valueOf(seconds));
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        clockThread.start();
    }

    private void initButtons() {
        runButton = new JButton("Run");
        pauseButton = new JButton("Pause");
        resetButton = new JButton("Reset");

        runButton.addActionListener(this);
        pauseButton.addActionListener(this);
        resetButton.addActionListener(this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == runButton) {
            isRunning = true;

            runButton.setEnabled(false);
            pauseButton.setEnabled(true);
            resetButton.setEnabled(false);
        } else if (e.getSource() == pauseButton) {
            isRunning = false;

            runButton.setEnabled(true);
            pauseButton.setEnabled(false);
            resetButton.setEnabled(true);
        } else if (e.getSource() == resetButton) {
            isRunning = false;
            timeValue.setText("0");
            seconds = 0;

            runButton.setEnabled(true);
            pauseButton.setEnabled(false);
            resetButton.setEnabled(false);
        }
    }
}

The problem is: if i remove the line System.out.println() (as i commented in the code above), the clock will not run. I have no idea why this is happening at all. Can someone explain it to me? Is it because System.out.println() affect running thread or something?

Advertisement

Answer

isRunning is not volatile and therefore the value each thread sees can diverge. You have a loop that just spins when the value is false and has no reason to go and check to see if the “real” value has change.

Note, you shouldn’t use Swing components (directly or indirectly) off of the AWT Event Dispatch Thread (EDT).

The simple fix is to make isRunning volatile and use EventQueue.invokeLater to invoke timeValue.setText.

Better would be to completely replace clockThread with a javax.swing.Timer (not java.util.Timer!), but that might be missing the point. At least make clockThread wait for isRunning to change.

Advertisement