简体   繁体   中英

Java synchronizing Threads with Swing

I've written a program which displays balls in a window which are moving and which absorb each other with a certain probability when getting in contact.

The current version works, the balls's movement is been calculated every time the paintComponent method is (implicitely) invoked:

public class ColliderPanel extends JPanel {
...

@Override
public void paintComponent(Graphics g){
    super.paintComponent(g);

    // calculate balls's movement
    Random r = new Random();
        ListIterator<Ball> it = cp.getColliderPanel().balls.listIterator();
        Vector<Ball> ballsToRemove = new Vector<Ball>();

        while (it.hasNext()) {
            Ball b = it.next();
            b.move(1.0 / 60.0);
            b.collideFrame(cp.getColliderPanel().getSize());

            ListIterator<Ball> it2 = cp.getColliderPanel().balls.listIterator(it.nextIndex());
            while (it2.hasNext()) {
                Ball b2 = it2.next();
                if (b.collide(b2)) {
                    if (r.nextDouble() < 0.5) {
                        if (b.m > b2.m) {
                            b.swallow(b2);
                            ballsToRemove.add(b2);
                        } else {
                            b2.swallow(b);
                            ballsToRemove.add(b);
                        }
                    }
                }
            }
        }

        cp.getColliderPanel().balls.removeAll(ballsToRemove);

        try {
            Thread.sleep((long) (1000.0 / 60.0));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    for(Ball b : balls) b.draw(g);

    repaint();
}

...
}

Now I want to outsource the calculation of the balls's movement to a second thread. I tried to create another class SimulateBallsMovement implements Runnable which does the calculation in the overriden run method and created a new Thread in ColliderPanel , which has SimulateBallsMovement as Runnable-object.

public class ColliderPanel extends JPanel {
private Thread simThread = new Thread(new SimulateBallsMovement());

@Override
public void paintComponent(Graphics g){
    super.paintComponent(g);

    // calculate balls's movement
    // what to to here? how to synchronize the painting and the calculation?

    for(Ball b : balls) b.draw(g);

    repaint();
}

...
}

My problem is that I don't know how to synchronize the painting of the balls and the movement calculation? Does ColliderPanel even need the Thread as a member? I just found tutorials on how the synchronize two threads which invoke the same method, but what do I want to do here?

The main thing to remember with Swing is that almost none of the Swing methods should be called from any other thread except the Swing Event Dispatch Thread (EDT).

The EDT is what sits in a loop, waiting for key presses, mouse clicks, and other events, and calling your handlers each time an event happens that interests your program.

Whenever any of your other threads wants to do something that will affect the GUI, it should call the SwingUtilities.invokeLater(r) method where r is some Runnable object. The invokeLater(r) method will post an event containing r to the event queue, and the EDT will handle the event by calling r.run() . The r.run() method can then safely call whatever Swing methods you need it to call.

一个建议是将计算移到 Swingworkers 后台处理方法中,并在 worker 的 done 方法中调用 repaint。

This looks like classic producer consumer scenario. The thread which calculates ball movements is producer and the thread which paints them is consumer. Check out these tutorial on the topic: http://www.tutorialspoint.com/javaexamples/thread_procon.htm or https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

To simplify the use of the AWT thread mechanism, and async component data fetching and component enable/disable while loading data, I wrote the ComponentUpdateThread, a long time ago. It makes it terribly easy to do updates of data and get the right things done in the right thread context.

https://github.com/greggwon/SwingUtil/blob/master/java/main/src1.5/org/wonderly/swing/ComponentUpdateThread.java

new ComponentUpdateThread( button1, button2 ) {
}.start();

is the basic premise of how you use it. All listened components will recursively be traversed and disabled by the actions of start(). There are three methods that you can implement.

The first one, setup(), is invoked by an AWT event thread, and should do anything to components (aside from disabling things that the cons parameters will make happen). This maybe simple things like emptying a list model etc.

The construct() method is invoked by an async, random thread. This method should "go get the data" to be used to populate controls, and put it into an appropriate container structure that it will return.

Finally, finished() is invoked by an AWT Event thread after construct() returns, and it should call getValue() to get the returned value from construct() activities, and then push that data into models/components as appropriate. finished() needs to call super.finished() at the right moment to "enable" the components passed into the cons. You can then disable things conditionally such as last selection in a list, options in checkboxes etc, and then return.

Here's an example of these methods taken from the javadocs. This shows the use of older APIs and doesn't include the fact that with generics, you can now make construct() a generic method with getValue() returning the same type. I have a version of this code that does all kinds of things that have been added more lately into Java.

This code is just to demonstrate the concepts around separation of thread use into separate methods so that you don't have to use SwingWorker directly, all over the place, but can use a more generic mechanism such as this.

My latest version of this code, includes the ability to chain together and next invocations so that more complex data retrieval and population can occur.

Ultimately, it would be really nice to just provide a ModelUpdater class that you could provide the component and any related model details to so that there was a compartmentalized use of data sourcing from remote access mechanisms.

    public void setup() {
        super.setup();
        list.setEnabled(false);
        list.clearSelection();
    }
    public Object construct() {
        try {
            Vector v = remote.getData();
            Collections.sort( v );
            return v;
        } catch( Exception ex ) {
            reportException(ex);
        }
        return null;
    }
    public void finished() {
        try {
            Vector v = (Vector)getValue();
            list.setListData(v);
        } finally {
            super.finished();
            list.setEnabled(true);
            edit.setEnabled(false);
            del.setEnaled(false);
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM