Handling Blocking Threads in Java

Handling Blocking Threads in Java

Francesca Sadikin
Francesca Sadikin

August 01, 2019

Thread Basics

Let's start from the beginning—what is a thread? A thread stores the information necessary for your computer to execute some part of your program. A program will always start with at least one thread, because that is how a program is executed. From here, you can create more threads to do additional work in parallel (at the same time) with the main thread.

A thread has multiple states:

  • New: Not yet started.
  • Runnable: Currently executing tasks.
  • Terminated: A terminated / dead thread.
  • States that have their execution paused, and what I think of as a "sleeping" thread:
    • Blocked: Waiting to acquire a mutually exclusive resource.
    • Waiting: Waiting indefinitely for another thread to perform a particular action.
    • Timed Waiting: Waiting for another thread to perform an action, but with a specified waiting time.

One of the tricky parts of multithreading is dealing with a sleeping or "blocked" thread—specifically a Waiting or Blocked thread, and especially when the rest of your program is waiting for it. If you don't have a strategy to handle this, your program will continue to spin waiting for that sleeping thread. This article will outline a method to handle these types of threads.

First—an analogy to understand the potential problems of a sleeping thread

Imagine that your mother texts you to go to the airport and pick up someone. You get your mother's message and head over to the airport, but there is no one to meet you, as the plane has been delayed. This will take a potentially unknown amount of time, so you decide to sleep while you wait, with only one way to be woken up: if the person you are here to pick up arrives.

While you sleep, your mother texts you again to come home. Your phone pings, but it doesn't wake you up and thus, you can't read the instructions. You continue sleeping waiting for the person you were supposed to pick up. If the plane is cancelled and that person never arrives, you could essentially sleep forever and never wake up.

An unhandled sleeping thread example

So how does this story connect to applications?

While it's perfectly normal for your thread to be sleeping when it's not doing anything, certain functions could trigger the unexpected side effect of the story above—in hopes of completing its task, a thread sleeps waiting for an "alarm" that never comes.

The example we will use for the rest of the article is that of BufferedReader.readLine(), a method to read from an Input Stream.

posts/2019-08-01-handling-blocking-threads-in-java/unhandled-sleeping.png

The above image diagrams a Main Thread unsuccessfully telling the Echo Session Thread to close itself. Because Echo Session Thread's BufferedReader.readLine() is not going to be complete until data is passed into the InputStream for BufferedReader to read, it will "sleep" / block the thread while it waits. Even if Main Thread tries to wake up the thread, it will not work as it's not the right "alarm" that Echo Session Thread is waiting for. This thread will continue waiting forever and only wake up once InputStream has data to read from.

Let's see how this blocking occurrence looks in code

The App class starts a thread (which implements an Echo Session) for each socket connection. As shown in the code sample below, we attempt to terminate the thread after the timer has run out by using thread.interrupt() to set an interrupt flag and unblock certain operations by triggering an InterruptedException; and applying thread.join(), which causes the main thread in App to stop executing until the child thread is terminated. However, the below EchoSession child thread has not been set up to respond to the interrupt call correctly.

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class App {
				public static void main() throws InterruptedException {
								long timeToWait = 1000 * 60 * 5; // 5 minutes
								long startTime = System.currentTimeMillis();

								// Starting client thread
								try (
												ServerSocket serverSocket = new ServerSocket(5000);
												Socket socket = serverSocket.accept()
								) {
												EchoSession echoSession = new EchoSession(socket);
												Thread thread = new Thread(echoSession);
												thread.start();

												// EchoSession reads data from the socket input 
												// and writes it back out through the socket output.
												// We try to use a while not interrupted loops
												// in hopes of receiving and executing the App's 
												// termination order, however, we will find that 
												// this code will remain blocked on 
												// BufferedReader.readLine().
												while (thread.isAlive()) {
																if (threadTimer(startTime, timeToWait, thread)) {
																				// Thread has outlived its timer
																				// Thus, Interrupt and terminate the thread
																				thread.interrupt();
																				thread.join();
																}
												}
								} catch (IOException e) {
												System.err.println("Error creating client.");
								}
				}

				private boolean threadTimer(long startTime, long timeToWait, Thread thread) {
								return ((System.currentTimeMillis() - startTime) > timeToWait) && thread.isAlive();
				}
}

EchoSession reads data from the socket input and writes it back out through the socket output. We try to use a while Thread is not interrupted condition in hopes of receiving and executing the App's termination order. However, we will find that this code will remain blocked on BufferedReader.readLine().


package java_echo_server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class EchoSession implements Runnable {
				private Socket socket;
				private BufferedReader bufferedReader;
				private PrintWriter printWriter;
				private boolean isRunning;

				public EchoSession(Socket socket) {
								try {
												InputStream inputStream = socket.getInputStream();
												this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
												this.printWriter = new PrintWriter(socket.getOutputStream(), true);
								} catch (IOException e) {
												System.err.println(e);
								}
				}

				public void run() {
								try {
												String inputLine;
												while (!Thread.interrupted() && (inputLine = bufferedReader.readLine()) != null) {
																String outputLine = "Echo Server: " + inputLine;
																printWriter.write(outputLine);
												}
												close();
								} catch (IOException e) {
												System.err.println(e);
								}
				}

				private void close() {
								try {
												isRunning = false;
												socket.close();
												System.out.println("Disconnecting client");
								} catch (IOException e) {
												System.err.println(e);
								}
				}
}

How can we tell the code above is a blocking call? Try this experiment:

  1. In App, set the timer to 10 seconds (or any short period of time) before the thread should be killed.
  2. Run the server code above in a terminal.
  3. Start a client connection to the server by typing nc localhost 5000 in a separate terminal, opening a space in the terminal for you to type a line that the server will echo back (do this only once!) terminal example
  4. Back in your server terminal, you will notice that the timer has run out and the main App has sent the call to kill the child thread. However, the thread will not terminate itself no matter how long you wait!
  5. In your client terminal, type another line. You will see that after entering the line, the thread finally terminates after reading and echoing back your input.

Handling a potentially sleeping thread

So, how can we adjust our code to handle a thread with a blocking call?

The answer is also a type of "sleeping" thread. By utilizing the Timed Waiting state, we can control the right "alarm" to wake the thread by using InterruptedException, BufferedReader.ready(), and Thread.sleep() to wait for input without blocking.

While the sleep state that occurs from BufferedReader.ready() and Thread.sleep() is the same as BufferedReader.readLine(), the way they were initiated and controlled are very different. As a reminder, BufferedReader.readLine() does not allow external control of its sleep state. Thread.sleep(), on the other hand, is one of the few functions that can be paired with InterruptedException as an "alarm" to wake up a "sleeping" thread any time we call Thread.interrupt() from an external controlling thread.

Essentially, by using a non-blocking function like BufferedReader.ready(), we can then use Thread.sleep() to create one-second windows of opportunity for the MainThread to interrupt the thread and trigger the InterruptedException code block defined by the programmer.

posts/2019-08-01-handling-blocking-threads-in-java/potentially-sleeping.png

Let's implement this strategy in a code sample

App stays the same—we just need to adjust our EchoSession code to continuously check to see if the client is ready to read from; and if not, it sleeps for one-second intervals. If the client IS ready to read from, it will echo back the input, then continue waiting and sleeping for the next set of input.

Interrupting this thread will execute the functions in the InterruptedException block, which in this case closes the session and returns.

public class EchoSession implements Runnable {
				// same member variables

				public EchoSession(Socket socket) {
								// same initialization as first EchoSession code above
				}

				public void run() {
								while (!Thread.interrupted() && isRunning) {
												try {
																// focus your attention here!!
																if (!bufferedReader.ready()) {
																				try {
																								Thread.sleep(1000);
																								continue;
																				} catch (InterruptedException e) {
																								close();
																								return;
																				}
																}
																String inputLine = bufferedReader.readLine();
																String outputLine = "Echo Server: " + inputLine;
																printWriter.println(outputLine);
																System.err.println(outputLine);
												} catch (IOException e) {
																System.err.println(e);
												}
								}
				}

				private void close() {
								// same as first EchoSession code above
				}
}

With this new EchoSession code, try running the experiment above—you will notice that the thread will be terminated when the timer runs out. No more blocking calls!