Blocking Operations and the GUI Thread
ConnectionHandler is a subclass of Thread that opens the network connection and reads incoming messages, keeping blocking network work off the GUI thread.
Why Network Work Gets Its Own Thread
The Asynchronous Network Chat Program uses a nested class named ConnectionHandler to handle most of its network-related work. ConnectionHandler is a subclass of Thread, and it has two jobs: opening the network connection, and then reading incoming messages once that connection exists. Understanding why this class is a thread, and not just an ordinary helper, is the key to reading the rest of the program.
Before you read any code, try a quick prediction. The Listen button creates a ConnectionHandler with port 5000. Does the constructor itself open a server socket on port 5000? Decide your answer before you continue.
What do you think happens?
When the Listen button creates a ConnectionHandler with port 5000, does the constructor open a server socket on port 5000?
Reveal answer
No. The constructor only sets instance variables, posts a status message, and starts the thread. The socket is created later, inside run().
Keeping the constructor short means the button click returns quickly. The slow work of opening the socket happens on the new thread, where it cannot freeze the window.
The Problem a Blocking Read Creates
Opening a connection is a blocking operation. It can take some time to complete. Reading incoming messages is also blocking: the program waits until data arrives. If that waiting happened on the same thread as the window, the program would be stuck waiting, and the window would stop responding to the user while it waited.
The design divides the labor cleanly. The GUI thread is responsible for responding to the user. The ConnectionHandler thread is responsible for waiting on the network. Placing the connection-opening code in its own thread means the GUI is not blocked while the connection is being opened. When you are debugging, most questions reduce to one question: which of these two threads does this line of code run on?
Two Constructors for Two Roles
The thread is created when the user clicks either the Listen button or the Connect button, and each button maps onto its own constructor. The Listen button makes the thread act as a server, using ConnectionHandler(int port). The Connect button makes the thread act as a client, using ConnectionHandler(String remoteHost, int port). The class has one constructor for each role.
| Constructor | What it does | What it does not do |
|---|---|---|
| ConnectionHandler(int port), server mode | Sets the state to LISTENING, stores the port, posts a status message to the transcript area, and starts the thread | Does not open a ServerSocket; that happens later in run() |
| ConnectionHandler(String remoteHost, int port), client mode | Sets the state to CONNECTING, stores the remote host and the port, posts a status message, and starts the thread | Does not open a Socket to the remote host; that happens later in run() |
Both constructors only set up fields and start the thread. The network work is deferred to run().
Tracing Execution from Constructor to run()
Now trace the full order of execution. The constructor and start() run on the thread that created the object, which is the GUI thread. Calling start() is what makes the new thread run: once started, the thread executes run(). Everything from run() onward runs on the ConnectionHandler thread. The flowchart below shows this boundary, which is the handoff from the GUI's work to the network's work.
Worked Trace: A Listen Click
Following one Listen click through the program
The user clicks Listen, which creates ConnectionHandler with port 5000. Trace where each step runs and when the server socket is opened.
- 1Constructor on the GUI thread
The server constructor sets the state to LISTENING, stores port 5000, and posts a status message to the transcript area. It does not open a server socket.
- 2start() on the GUI thread
start()makes the new thread run. The GUI thread is free to keep responding to the user. - 3run() on the ConnectionHandler thread
run()checks the state and sees the server role. It creates a ServerSocket on port 5000, accepts one connection, and closes the listener. - 4connectionOpened()
This sets up the connection, including creating the
BufferedReadernamed in. - 5Reading loop
While the state is CONNECTED, each pass calls
in.readLine(). A non-null line goes toreceived(input). A null result triggersconnectionClosedFromOtherSide(). - 6finally block
Whether the loop ended normally or an exception occurred,
cleanUp()runs before the thread terminates.
The socket was opened on the ConnectionHandler thread, not during the button click. The window stayed responsive while the thread waited for a connection.
Mistakes Beginners Make When Reading This Code
Common mistake Assuming the constructor opens the socket
What it looks likeExpecting a server socket on port 5000 to exist as soon as the Listen button is clicked.
Why it failsThe constructor only sets instance variables, posts a status message, and starts the thread.
Do this insteadLook in run() for the ServerSocket or Socket creation.
Common mistake Thinking start() runs run() on the current thread
What it looks likeBelieving run() executes inline, so it would block the button handler.
Why it failsCalling
start()is what makes the new thread run, and the thread then executesrun().Do this insteadTreat everything from run() onward as running on the ConnectionHandler thread.
Common mistake Expecting every exception to appear in the transcript
What it looks likeClosing the connection on purpose and then wondering why no error appears.
Why it failsThe catch block reports errors only if the state is not CLOSED.
Do this insteadCheck the state before deciding an error was lost.
Common mistake Confusing in.readLine() returning null with an error; null means the other side closed the connection
Locating the Code for a Symptom
When something goes wrong, first ask which thread the suspicious line runs on. Then use the structure you have just traced to narrow the search. The table maps common symptoms to the part of the design most likely to be involved, based on the stages described in the source.
| Symptom | Where to look first | Why |
|---|---|---|
| Window freezes while connecting | Any blocking call that runs on the GUI thread, such as code in the constructor or start() path | Opening a connection and reading are blocking; they belong on the ConnectionHandler thread |
| Connection never appears to open | The server or client branch in run() and connectionOpened() | The socket and BufferedReader are created in these stages |
| Incoming message does not show | The reading loop, in.readLine() and received(input) | Each non-null line is passed to received(input) |
| Peer disconnect shown unexpectedly | The null check and connectionClosedFromOtherSide() | A null read means the other side closed the connection |
| Missing error message | The catch block and its check for the CLOSED state | Errors are reported only when the state is not CLOSED |
| Resource left open after failure | The finally block and cleanUp() | This is the single place where teardown happens |
Use the thread boundary and the run() stages to choose the first place to inspect.
A window freezes right after the user clicks Connect. Using the thread boundary, explain which thread should be doing the network work, which constructor was involved, and which method you would inspect first. Then explain why the error might be hidden if the connection was closed on purpose.
Hints
- Ask which thread runs the constructor and which runs
run(). - The client constructor takes a remote host and a port.
- Check the state before deciding whether an error should be reported.
Key Takeaways
ConnectionHandleris a Thread subclass that opens the network connection and reads incoming messages, keeping blocking network work off the GUI thread.- The Listen button uses
ConnectionHandler(int port)and the Connect button usesConnectionHandler(String remoteHost, int port); both constructors only set fields, post a status message, and start the thread. start()makes the new thread runrun(), which opens the ServerSocket or Socket, callsconnectionOpened(), loops onin.readLine()while the state is CONNECTED, and finally callscleanUp().- Errors reach the user through the catch block only when the state is not CLOSED, and the finally block is the single place where teardown happens.
- When debugging, ask which thread a line runs on: the GUI thread responds to the user, and the
ConnectionHandlerthread waits on the network.