This page looks best with JavaScript enabled

Keeping your threads under control

 ·  ☕ 5 min read  ·  🏍 Zisis

Introduction

We are so spoiled by Java’s concurrency utilities that it’s easy to ignore some basic error handling like what happens when some of the threads we have created “go wild”.

It’s like getting on a fast motorcycle for the first time. Thrilled by the idea of speed you roll on the throttle reaching >200Km/h in no time but then a car crops up out of nowhere. It’s only then that you realize you have no idea how to apply the brakes. Nasty situation with a bad ending most of the times.

Similarly with concurrency, you can get something “working” pretty quick, but you still have to put some thought on how to deal with those threads in case something goes wrong!

Vanilla scenario

The most common examples involving threads are trivial in the sense that

  • A thread pool is created
  • Some relatively simple tasks are submitted to it
  • Shutdown is issued in the end
  • All results are available and you move on

That could look like this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import org.apache.commons.lang3.RandomStringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class InterruptionPolicyExample {

    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        List<Future<String>> futures = new ArrayList<>();

        futures.add(service.submit(() -> {
            StringBuilder testString = new StringBuilder();
            testString.append(RandomStringUtils.randomAlphabetic(100));
            return testString;
        }));

        gatherResults(futures);

        shutdownAndAwaitTermination(service);
    }

    private static void gatherResults(List<Future<String>> futures) {
        for (Future<String> future : futures) {
            try {
                String result = future.get();
                System.out.println("result = " + result);
            } catch (InterruptedException | ExecutionException e) {
                System.out.println(e.getClass());
            }
        }
    }

    static void shutdownAndAwaitTermination(ExecutorService pool) {
        pool.shutdown(); // Disable new tasks from being submitted
        try {
            // Wait a while for existing tasks to terminate
            if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
                pool.shutdownNow();
                // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if (!pool.awaitTermination(10, TimeUnit.SECONDS))
                    System.err.println("Pool did not terminate");
            }
        } catch (InterruptedException ie) {
            // (Re-)Cancel if current thread also interrupted
            pool.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }
}

We create a single-threaded pool and submit a task that just creates and returns a random String. Everything goes smooth. One worthy point to note here is that we need to properly clean-up after our thread pool by calling .shutdownAndAwaitTermination() , otherwise the program will never exit.

Getting busy

Imagine now that the logic inside your thread enters an infinite while loop or gets into a situation that involves excessive CPU or memory usage. We’ll keep it simple here and go with a nice traditional infinite loop

1
2
3
4
5
6
futures.add(service.submit(() -> {
     StringBuilder test = new StringBuilder();
	 while(true) {
       	test.append(RandomStringUtils.randomAlphabetic(100));            	
	 }            
}));

The above can bring a server down on its own. Imagine an operation in the real world that operates on character level and accepts tons of text in a single request. Now on top of that picture a busy web application which accepts lots of similar requests and you have a recipe for disaster. Threads are busy consuming resources and your web server eventually crashes. Now what?

Please respond cut #1

First reaction is to add a timeout when waiting for the thread’s result as in

1
String result = future.get(5000, TimeUnit.MILLISECONDS);

Still the program hangs. We got the TimeoutException alright but the thread keeps running and chipping away memory.

Please respond cut #2

OK, Timeout won’t do it. Let’s be more pushy on that and also cancel the task - which sends an interruption message - after the timeout of 5sec.

1
2
3
4
5
6
7
try {
	String result = future.get(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    System.out.println(e.getMessage);
} finally {
	future.cancel(true);
}

Still nothing, but why? Java can and will deliver the interruption to the corresponding thread but you still must be responsive to interruption. We know that blocking methods are responsive to interruption (like .wait() or BlockingQueue’s put() and take() ) but what about threads that do not make use of such methods.

Please respond cut #3

Remember that interruption is a co-operative mechanism, so we have to do our part. In order to make the above example work we need to react on the interruption

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
futures.add(service.submit(() -> {
	StringBuilder test = new StringBuilder();
	
	while(true) {
		test.append(RandomStringUtils.randomAlphabetic(100));
		if (Thread.currentThread().isInterrupted()) {                    
			Thread.currentThread().interrupt();
			break;
		}

	}			
	return "Finished work in thread";
}));

The interrupted status of the thread is set by future.cancel(true) but one has to check for this using Thread.currentThread().isInterrupted() and act accordingly. You also have to set the interrupted status back to the original value since we are not the owners of the thread.

Takeaways

Your concurrent applications will be far more robust if you ensure the following

  • Make sure your threads are responsive to interruption
  • Never forget to shutdown your thread pools, or register them to get called during JVM shutdown

Your need to tick both boxes above in order to properly clean up your threads and exit the JVM. This is especially important in CLI tools for example which run for some time and exit after each call. For long running applications like a web server you can register your shutdown hook through Runtime.getRuntime().addShutdownHook.

Share on

Zisis Tachtsidis
WRITTEN BY
Zisis
Software Engineer, Husband to one, Father of two, Rider of two (motorcycles)