4

While writing a program to MIDI with a Swing interface, I experienced a hang, such that kill -9 is required. It is 100% reproducible by running the following program as java MidiSwingProblem hang0

import java.lang.reflect.InvocationTargetException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MidiSwingProblem {
    /**
     * JFrame never appears.  Hangs such that `kill -9` is required.
     */
    public static void hang0() throws MidiUnavailableException {
        Synthesizer synth = MidiSystem.getSynthesizer();
        synth.open();
        JFrame frame = new JFrame("MIDI Swing Hang 1");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    /**
     * JFrame never appears.  Hangs such that `kill -9` is required.
     */
    public static void hang1() throws MidiUnavailableException {
        Synthesizer synth = MidiSystem.getSynthesizer();
        synth.open();
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("MIDI Swing Hang 2");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }

    public static void solution0() throws MidiUnavailableException {
        // It doesn't matter whether .getSynthesizer() or new JFrame() is
        // called first.  It seems to work as long as synth.open() happens
        // after new JFrame().
        Synthesizer synth = MidiSystem.getSynthesizer();
        JFrame frame = new JFrame("MIDI Swing Solution 0?");
        synth.open();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void solution1() {
        new Thread() {
            public void run() {
                try {
                    Synthesizer synth = MidiSystem.getSynthesizer();
                    synth.open();
                } catch (MidiUnavailableException noMidi) {
                    noMidi.printStackTrace();
                }
            }
        }.start();

        JFrame frame = new JFrame("MIDI Swing Solution 1?");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void solution2() {
        new Thread() {
            public void run() {
                try {
                    Synthesizer synth = MidiSystem.getSynthesizer();
                    synth.open();
                } catch (MidiUnavailableException noMidi) {
                    noMidi.printStackTrace();
                }
            }
        }.start();

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("MIDI Swing Solution 2?");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }

    public static void main(String[] args) throws NoSuchMethodException
                                                , IllegalAccessException
                                                , InvocationTargetException {
        MidiSwingProblem.class.getMethod(args[0], new Class[0]).invoke(null);
    }
}

I assume that there is a deadlock in hang0(), and that it's my fault rather than a bug in J2SE. (I've verified the behaviour on Java 1.7 and 1.8 on OS X.)

I have three questions:

  1. Taking a hint, I also tried writing it as hang1(), but that didn't work. Why is SwingUtilities.invokeLater() insufficient?
  2. If I rearrange the lines (see solution0()) to call synth.open() after new JFrame(), then it works! Why? Is solution0() proper, or am I just getting lucky? It seems like a flimsy solution to me.
  3. For good measure, I've also written solution1() and solution2(), both of which do not seem to hang. Are these versions more correct than solution0(), or are they overkill? Having the synth object in a separate thread makes it hard for the rest of the program to use it.
Community
  • 1
  • 1
200_success
  • 7,286
  • 1
  • 43
  • 74

1 Answers1

4

In case of thread there is no guarantee that what thread will run first.

So I suggest you to write the code in SwingUtilities.invokeLater() or EventQueue.invokeLater() to make sure that EDT is initialized properly.

Read more

sample code:

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        try {
            Synthesizer synth = MidiSystem.getSynthesizer();
            synth.open();
        } catch (MidiUnavailableException e) {
            e.printStackTrace();
        }

        JFrame frame = new JFrame("MIDI Swing Solution 1?");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
});
Community
  • 1
  • 1
Braj
  • 46,415
  • 5
  • 60
  • 76