Swing and the Event Thread
In a typical Java program with a UI, there are (initially) two threads running,
the main thread of the program and, once the UI is made visible, the event
thread. The event thread performs a loop that listens for user
events (keyboard and mouse input), processes the event by calling the necessary
component methods, which notify the appropriate listeners in the program
by calling actionPerformed
, mousePressed
, etc.,
and then waits for the next user event. The execution of the listener
methods also occurs in the event thread. As we discussed with respect
to Vector
and ArrayList
, there is significant
lock management overhead when declaring a method synchronized
. For this reason, none of the Swing component classes' methods are
synchronized. The reason this matters to programmers is that you should
never manipulate components in any way from another thread than the event
thread. For example, if you have a thread that attempts to do a
setText
on a JTextField
and simultaneously the user
clicks or types in the field or tabs through the field, or the UI attempts
to repaint the field, a race condition or a deadlock can occur.
So suppose another thread (say, one that is waiting for a response for a
socket read
), needs to call a method that ultimately will make
some component calls (e.g., to set a field or repaint). How can you
ensure that that method call occurs in the event thread? The class method
EventQueue.invokeLater
takes a Runnable
and adds
an event that runs it as the last item in the event queue. In practice,
this runnable will be the next item executed by the event thread when it
finishes processing any current event because the user is not issuing events
at microsecond speeds. For example:
EventQueue.invokeLater(new Runnable() {
public void run() {
battleshipUI.enablePlayComponents(false);
battleshipUI.shootAt(row, column);
}
});
Waiting for the opponent's move
Another issue for the first programming assignment is that a simple way
of coding the program is to make the socket calls in (or from a call in)
a component listener. For example, you might have the UI wait for the
opponent's move by issuing a read call to the socket which will block until
the opponent writes a move through the socket. In this case, you are
blocking the event thread, which means that the UI cannot respond to the
user. This will function acceptably on the client (except that he/she
will not be able to quit), although it's ugly: the button doesn't pop back
out and if you put another window over your UI and then move it off, your
UI will be blank. Both effects occur because repaint events are queued
but not executed since the thread is blocked. Furthermore, this won't
work for the server, which must be able to play more than one game at a time.
The server must start a separate thread for each client so that it
can continue to accept connections, and must display a separate UI for each
game. However, there is one event thread for all Swing components displayed,
not one for each frame. To prevent making a move while the player should
be waiting for the opponent to make a move, disable the row and column text
fields and the "Place Hit" button. You must disable the individual
components, not the just the panel that contains them (i.e., disable does
not propagate to child components). Also, as you know, the setup components
should be disabled while the game is being played.