Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code not executed without a print statement [duplicate]

Tags:

java

java-7

swing

i've been making a countdown program, and i came up with this.

package main;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class Gatoo extends JFrame implements ActionListener {
    private int sec, min, secTot, since = 999;
    private long lastTime;

    private JTextField mm = new JTextField(2), ss = new JTextField(2);
    private JLabel minLab = new JLabel("Minutes:"), secLab = new JLabel(
            "Seconds:");
    private JButton start = new JButton("Start");

    private Clip done;
    private boolean started = false;

    private static final long serialVersionUID = 4277921337939922028L;

    public static void main(String[] args) {
        Gatoo cake = new Gatoo("Title");
        cake.pack();
        cake.setSize(800, 600);
        cake.setLocationRelativeTo(null);
        cake.setDefaultCloseOperation(3);
        cake.setVisible(true);
        cake.run();
    }

    public Gatoo(String s) {
        super(s);
        setLayout(new FlowLayout());

        start.addActionListener(this);

        add(minLab);
        add(mm);
        add(secLab);
        add(ss);
        add(start);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        started = true;
    }

    public void play(File file) throws MalformedURLException,
            UnsupportedAudioFileException, IOException,
            LineUnavailableException {
        AudioInputStream ais = AudioSystem.getAudioInputStream(new File(
                "lib/done.wav"));
        DataLine.Info info = new DataLine.Info(Clip.class, ais.getFormat());
        done = (Clip) AudioSystem.getLine(info);
        done.open(ais);
        done.start();
    }

    public void run() {
        while (true) {
            System.out.print("");// needed?
            if (started) {
                try {
                    min = Integer.parseInt(mm.getText());
                    sec = Integer.parseInt(ss.getText());
                    secTot = (min * 60) + sec;
                    lastTime = System.currentTimeMillis();
                    while (secTot > 0) {
                        since = (int) (System.currentTimeMillis() - lastTime);
                        if (since > 998) {
                            lastTime = System.currentTimeMillis();
                            secTot--;
                        }
                    }

                    play(new File("done.wav"));

                } catch (NumberFormatException exception) {
                    System.out.println("Minutes and seconds must be numbers.");
                    return;
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
                started = false;
            }
        }
    }
}

In the while loop at the end the countdown code doesn't execute without a print / println statement inside. How come? The program works perfectly fine with the print statement though.

like image 516
user3092987 Avatar asked Dec 26 '13 14:12

user3092987


2 Answers

First and foremost, your program is thread-unsafe because boolean started is a shared variable, but it is neither volatile nor accessed within synchronized blocks.

Now, accidentally, PrintStream#print is a synchronized method and, on any actual architecture, entering and exiting a synchronized block is implemented using memory barrier CPU instructions, which cause a complete synchronization between the thread-local state and main memory.

Therefore, by pure accident, adding the print call allows the setting of started flag by one thread (the EDT) to be visible by another (the main thread).

like image 72
Marko Topolnik Avatar answered Sep 16 '22 23:09

Marko Topolnik


You have poor design for Swing application.

  1. Don't use while(true) loop in your run() method. Read more about Concurency in Swing.
  2. Call events with help of Listeners(ActionListener e.g.) instead of flags(started here).
  3. Instead of counting time use Swing Timer.

Change your run() method like next:

public void run() {
      min = Integer.parseInt(mm.getText());
      sec = Integer.parseInt(ss.getText());
      secTot = (min * 60) + sec;
      Timer timer = new Timer(1000*secTot, new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
              try {
                play(new File("done.wav"));
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    });
      timer.start();
}

actionPerformed() method :

@Override
public void actionPerformed(ActionEvent e) {
    run();
}

and remove cake.run() in main method.

like image 20
alex2410 Avatar answered Sep 16 '22 23:09

alex2410