Search code examples
javaswingconcurrencyswingworkerevent-dispatch-thread

Concurrency in Swing with While Loops


I've been reading about Java Concurrency in Swing, and cannot find a suitable answer to my situation.

Goal: To have my swing application run at the same time the while loop is until the program / application is quit.

Problem: The program freezes (as I anticipated beforehand) and cannot be used.

Attempted-Solution: I tried to start the while loop on another thread, started it, still nothing.

Code:

package stakeme;

import java.io.IOException;

import javax.swing.SwingUtilities;

public class StakeMeServer {

    private static ServerSocket socket;

    public static Thread t;


    public static void main(String[] args) throws IOException {


        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ConsoleGUI("Server", 800, 400, false);
            }

        });

        Thread t = new Thread(() -> {
            System.out.println("1");
            while (online) {
                System.out.println("2");
            }
        });
        t.setName("GameServer");
        t.start();
    }

}



Update: By using the SwingWorker class to do my while loop in the background, I was able to do almost what I needed to do.

Code:

package stakeme;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class StakeMeServer {

    private static AtomicBoolean online = new AtomicBoolean(false);

    public static GameServerLoop gsl = new GameServerLoop();


    public static void main(String[] args) throws IOException {

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {

                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
                    e.printStackTrace();
                }

                new ConsoleGUI("Server", 800, 400, false);

            }

        });

        //this should always be running during the life of the program
        gsl.execute();

    }

    static class GameServerLoop extends SwingWorker<Void, Void> {

        @Override
        protected Void doInBackground() {
            System.out.println("1");
            while (true) {
                while (online.get()) {
                    System.out.println("2");
                }
            }
        }

    }

}



Everything works, to a fault. Running this at processing speeds (while loop) using the System.out.println() method will make the program some-what unresponsive. Clicking the stop button will work.. Eventually. It seems to be that everything is backed up because of the massive amount of System.out.println() calls.


Solution

  • So you incomplete example leaves a lot of questions unanswered, basically, something like this..

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.util.concurrent.atomic.AtomicBoolean;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class StakeMeServer {
    
        private ServerSocket socket;
        private Thread t;
        private AtomicBoolean online = new AtomicBoolean(false);
    
        public static void main(String[] args) throws IOException {
            new StakeMeServer();
        }
    
        public StakeMeServer() {
    
            SwingUtilities.invokeLater(new Runnable() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }
    
                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            });
    
            open();
    
            Thread t = new Thread(() -> {
                System.out.println("1");
                while (online.get()) {
                    System.out.println("2");
                }
            });
            t.setDaemon(true);
            t.setName("GameServer");
            t.start();
        }
    
        public void open() {
            System.out.println("Starting Server...");
            online.set(true);
            System.out.println("Server Online");
        }
    
        public void close() {
            System.out.println("Stopping Server...");
            online.set(false);
            System.out.println("Server Offline");
        }
    
        public class TestPane extends JPanel {
    
            private int x = 0;
            private int delta = 4;
    
            private JLabel state;
    
            public TestPane() {
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        x += delta;
                        if (x + 10 > getWidth()) {
                            x = getWidth() - 10;
                            delta *= -1;
                        } else if (x < 0) {
                            x = 0;
                            delta *= -1;
                        }
                        state.setText(Boolean.toString(online.get()));
                        repaint();
                    }
                });
                timer.start();
    
                setLayout(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridwidth = GridBagConstraints.REMAINDER;
    
                state = new JLabel(Boolean.toString(online.get()));
                add(state, gbc);
    
                JButton btn = new JButton("Stop");
                btn.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        btn.setEnabled(false);
                        close();
                    }
                });
                add(btn, gbc);
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setColor(Color.RED);
                g2d.fillOval(x, (getHeight() / 2) - 5, 10, 10);
                g2d.dispose();
            }
    
        }
    
    }
    

    works just fine.

    I used an AtomicBoolean instead of boolean to prevent possible dirty read/writes between threads and made the Thread a daemon thread, so the application can close and the thread will not keep the JVM from terminating.

    Personally, I'd lean towards using a SwingWorker, as it provides functionality which allow you to synchronize updates back to the UI simply

    Take a look at Concurrency in Swing and Worker Threads and SwingWorker for more details